- Published on
Day18: Learning TypeScript: Narrowing Part 2
- Authors

- Name
- irisjustdoit
- @irisjustdoit
Day18: Learning TypeScript: Narrowing Part 2
Alright, let's continue taking notes on Narrowing. What other methods can be used to narrow types? If there are any errors, please feel free to leave comments. Thank you!
Using Type Predicates
- If we want to define a custom type guard, we can define a function that returns a type predicate.
- A type predicate refers to
parameterName is Type. In the following example,pet is Fishis a type predicate. parameterNamemust be the name of the parameter in the function; in the example below, the parameter name ispet, soparameterNamewill bepet.- The function will return true or false; if true, it indicates that the variable conforms to the type predicate.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
// pet can be of type Fish or Bird
// isFish function returns a type of: pet is Fish, indicating whether it returns true or false
// When it returns true, it is Fish
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined; // (pet as Fish) asserts that pet is of type Fish, so swim will not be undefined
}
Discriminated Unions
TypeScript can automatically determine types based on the same identifiable properties and perform different operations accordingly. It has three elements:
- Each interface needs to have the same identifiable property, such as
kind. - Use a type alias to declare a union type, which includes the types, such as
type Shape = Circle | Square;. - Use a type guard, such as
shape.kind === "circle".
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
}
}
let area = getArea({ kind: "circle", radius: 5 });
console.log(area);
Usable in Backend Response
According to PJ's article, this is very useful when receiving responses from the backend. If the status is OK, the data will carry a payload; otherwise, the data will carry an error. We can define multiple different types that share common fields, such as status, and use this field to determine what other properties exist.
In cases where no determination is made, we can use exhaustiveness checking, using the never type to indicate states that should not exist, which is generally used for error handling.
interface ISuccessResp {
status: 'OK'; // common field status
payload: unknown;
}
interface IErrorResp {
status: 'ERROR';
errorCode: number;
description: string;
}
type Resp = ISuccessResp | IErrorResp;
const parseResponse = (resp: Resp) => {
switch (resp.status) {
case 'OK': // through narrowing, we know it's of type ISuccessResp
return resp.payload; // then use the payload property of ISuccessResp
case 'ERROR': // through narrowing, we know it's of type IErrorResp
return resp.description; // then use the description property of IErrorResp
default:
const _exhaustiveCheck: never = resp;
return _exhaustiveCheck;
}
};
I went out to celebrate a friend's birthday after a long time, it was a happy reunion XD (Many of my good friends have October birthdays, happy birthday to all the Libras!) But when I got home and saw the article stock dwindling day by day, I felt scared. Well, tomorrow is Monday again, let's work hard together 💪
References
https://pjchender.dev/typescript/ts-narrowing/#user-defined-type-guards https://www.typescriptlang.org/docs/handbook/2/narrowing.html https://ithelp.ithome.com.tw/articles/10222470 https://blog.csdn.net/weixin_34088583/article/details/92693893 https://zh.wikipedia.org/wiki/%E6%A0%87%E7%AD%BE%E8%81%94%E5%90%88
