Narrow types use cases
![Narrow types use cases](/content/images/size/w2000/2022/05/2022-05-05-09-49-57.png)
TypeScript union types represent multiple types at the same time.
It is convenient to group types into unions to explain they share something in common, they have common purpose (think of redux reducer
which receives an action object of many different shapes).
For TypeScript there is an awesome technique called type narrowing
Type narrowing - technique that allows a compiler (and developer via IDE) to understand which exact type (shape) is present inside code block.
How to narrow types
Let's say we have union of two other types like below:
type SignInAction = {
type: 'sign-in';
email: string;
password: string;
};
type VerifyEmailAction = {
type: 'verify-email';
email: string;
otp_code: string;
};
type Action = SignInAction | VerifyEmailAction;
Now, we can narrow type of union via runtime check
:
const handleAnyAction = (action: Action) => {
if (action.type === 'sign-in') {
// β
fully typed
return action.password;
}
if (action.type === 'verify-email') {
// β
fully typed
return action.otp_code;
}
};
Also, we can narrow typein types context only
viabuilt-in utility type Extract
:
// β
structure is same as for VerifyEmailAction
type ActionWithOtpCode = Extract<Action, { otp_code: string }>;
Also, we create a generic guard
which will safely narrow types like that:
const guardForActionType = <T extends Action['type']>(
action: Action,
type: T
): action is Extract<Action, { type: typeof type }> => action.type === type;
const handleEmailVerification = (action: Action) => {
// makes sure that action is of type VerifyEmailAction
if (guardForActionType(action, 'verify-email')) {
// β
fully typed
return action.otp_code;
}
throw new Error('invalid action provided');
};
Type narrowing via generic guard is especially useful for complex types (shapes) where you need to check multiple fields.