How Type Narrowing Works in Typescript
Updated at
Type narrowing is the process in which typescript can clearly determine that an entity has a certain type.
A very basic example would be:
0type MyType = string | number12function myFunction(prop: MyType) {3 if(typeof prop === "number") {4 // handle it like a number5 } else {6 // handle it like a type7 }8}
However, a more sophisticated example for type narrowing in typescript might look like this:
Assume that an API returns a status field that determines which other fields will be present in the response. In the first image, you can see a response where status: "VALID" includes fieldA, and another response where status: "INVALID" includes fieldB. Both might include a sharedFieldC, but it is optional.
0const responseA: Response = {1 status: "VALID",2 fieldA: false3}45const responseB: Response = {6 status: "INVALID",7 fieldB: false8}
To clearly define this behavior in TypeScript, you can create an abstract type for shared fields and then define explicit variants of your response for each individual status.
In the second image, TypeScript uses type narrowing in the if/switch statement to infer which fields are available based on the status field.
0type AbstractApiResponse = {1 status: "VALID" | "INVALID"2 sharedFieldC?: boolean3}45type ValidResponse = AbstractApiResponse & {6 status: "VALID",7 fieldA: boolean8}910type InvalidResponse = AbstractApiResponse & {11 status: "INVALID",12 fieldB: boolean13}1415type Response = InvalidResponse | ValidResponse1617function responseHandler(response: Response) {18 if(response.status === "VALID") {19 response.fieldA20 response.fieldB // This will not compile21 } else {22 response.fieldA // This will not compile23 response.fieldB24 }25}
What Do You Gain From Type Narrowing In Typescript?
- No need for assertion operators like ! or ?
- Simple setup for a clearly defined API response structure
- Improved readability by using TypeScript's type inference