Unions and narrowing¶
This lesson covers union types, intersection types, type guards, discriminated unions, and how to safely work with values that can be more than one thing.
Union types¶
A union says a value can be one of several types.
You can call only the methods that exist on every member of the union.
Trying to call id.toUpperCase() would fail because number does not have that method.
Intersection types¶
An intersection requires a value to satisfy all combined types.
A Person must have both name and age.
Narrowing with typeof¶
Use type guards to narrow a union to a single member.
function format(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase()
}
return value.toFixed(2)
}
Inside the if block, value is narrowed to string. In the else, it is number.
Narrowing with in¶
type Circle = { kind: 'circle'; radius: number }
type Square = { kind: 'square'; side: number }
type Shape = Circle | Square
function area(shape: Shape): number {
if ('radius' in shape) {
return Math.PI * shape.radius ** 2
}
return shape.side ** 2
}
Discriminated unions¶
Give each member a shared literal property called the discriminant.
type Result<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string }
function message<T>(result: Result<T>): string {
switch (result.status) {
case 'idle':
return 'Ready'
case 'loading':
return 'Loading...'
case 'success':
return `Got ${JSON.stringify(result.data)}`
case 'error':
return result.message
}
}
The switch on status narrows result inside each case. TypeScript knows whether data or message is available.
Excess property checks¶
Object literals are checked for extra properties beyond the declared type.
type Point = { x: number; y: number }
const p: Point = { x: 1, y: 2, z: 3 } // error: excess property z
This does not happen when assigning an existing variable because excess property checking only applies to fresh object literals.
Hands-on: shape area calculator¶
- Create
src/shapes.ts. - Define a discriminated union
Shapewith membersCircle,Rectangle, andTriangle. CircleusesradiusRectangleuseswidthandheightTriangleusesbaseandheight- Write
function area(shape: Shape): numberwith aswitch. - Create an array of mixed shapes and use
mapto compute all areas. - Write
function totalArea(shapes: Shape[]): numberusingreduce. - Add a fourth shape
Triangleonly if the previous switch handled it with an explicit case. Try adding an unhandled case and see the compiler warn.