TypeScript
Beginner
1 min read
Discriminated Unions and Exhaustive Narrowing
Example
// Remote data — discriminated union for async state
type RemoteData<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: string };
interface Post { id: number; title: string }
function renderPost(state: RemoteData<Post>): string {
switch (state.status) {
case "idle": return "Not started";
case "loading": return "Loading...";
case "success": return `Post: ${state.data.title}`;
case "error": return `Error: ${state.error}`;
default:
const _exhaustive: never = state;
throw new Error(`Unhandled state: ${JSON.stringify(_exhaustive)}`);
}
}
// Narrowing with multiple discriminants
type UserAction =
| { type: "LOGIN"; payload: { userId: number; token: string } }
| { type: "LOGOUT" }
| { type: "UPDATE"; payload: { field: string; value: unknown } };
function reducer(action: UserAction): string {
switch (action.type) {
case "LOGIN":
return `User ${action.payload.userId} logged in`;
case "LOGOUT":
return "User logged out";
case "UPDATE":
return `Updated ${action.payload.field}`;
}
// TypeScript knows we've covered all cases — no default needed
}
// Type narrowing with control flow
function processValue(x: string | number | string[]) {
if (Array.isArray(x)) {
return x.join(", "); // string[]
} else if (typeof x === "string") {
return x.trim(); // string
} else {
return x.toFixed(2); // number
}
}
Related Resources
TypeScript Reference
Complete tag & property list
TypeScript How-To Guides
Step-by-step practical guides
TypeScript Exercises
Practice what you've learned
More in TypeScript