Overview
TypeScript provides a plethora of advanced types and features which aid in writing robust and type-safe code. One such feature is the use of discriminated unions, also known as tagged unions or algebraic data types. These are an elegant way to handle different types under a common interface. In this guide, we will explore the power of discriminated unions and how to effectively use them in your TypeScript projects.
Discriminated unions in TypeScript provide a way to combine multiple distinct types into a single type, using a common property to discriminate between them, ensuring that the correct type information is available at compile time and facilitating type-safe conditional code paths.
Basic Example of Discriminated Unions
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Circle {
kind: 'circle';
radius: number;
}
type Shape = Square | Rectangle | Circle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'square':
return shape.size * shape.size;
case 'rectangle':
return shape.width * shape.height;
case 'circle':
return Math.PI * shape.radius ** 2;
default:
throw new Error('Unknown shape')
}
}
The above example illustrates a basic discrimination between shapes.
Advanced Usage of Discriminated Unions
interface Deposit {
type: 'deposit';
amount: number;
}
interface Withdrawal {
type: 'withdrawal';
amount: number;
}
interface Transfer {
type: 'transfer';
from: number;
to: number;
amount: number;
}
type Transaction = Deposit | Withdrawal | Transfer;
function processTransaction(transaction: Transaction) {
switch (transaction.type) {
case 'deposit':
// Handle deposit
break;
case 'withdrawal':
// Handle withdrawal
break;
case 'transfer':
// Handle transfer
break;
}
}
This example shows indiscriminated union handling different types of banking transactions, with the common discriminator being the ‘type’ attribute.
Dealing with Exhaustiveness Checking
function assertNever(x: never): never {
throw new Error('Unexpected object' + x);
}
function getTransactionMessage(transaction: Transaction): string {
switch (transaction.type) {
case 'deposit':
return `Deposited ${transaction.amount}`;
case 'withdrawal':
return `Withdrew ${transaction.amount}`;
case 'transfer':
return `Transferred ${transaction.amount} from ${transaction.from} to ${transaction.to}`;
default:
return assertNever(transaction);
}
}
By using a helper function like assertNever, TypeScript can ensure that all cases are handled.
Pattern Matching with Discriminated Unions
In functional languages, pattern matching allows for elegant handling of discriminative data. TypeScript does not have built-in pattern matching, but discriminated unions can mimic this.
Integration with Other TypeScript Features
Discriminated unions can be used in tandem with other TypeScript features such as generics and utility types, providing even greater flexibility and safety.
Conclusion
Discriminated unions in TypeScript offer a powerful tool for writing flexible and type-safe code. By mastering their use, you can handle complex data structures and logic in a more legible and maintainable way, resulting in a more robust application.