Dùng discriminated union thay vì một object với nhiều field optional.
Union có một field chung làm discriminant (thường là status), giúp compiler biết chính xác field nào tồn tại ở mỗi nhánh.
ts
type ScreenState =
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; message: string }
function render(s: ScreenState) {
switch (s.status) {
case 'loading': return '...'
case 'success': return s.data.length // data chắc chắn có
case 'error': return s.message // message chắc chắn có
}
}Vì sao hơn cách { isLoading, data?, error? }: cách kia cho phép các tổ hợp vô nghĩa như isLoading: true mà vẫn có error, và mọi lần đọc data đều phải kiểm tra undefined. Union loại bỏ các trạng thái không hợp lệ ngay ở tầng type.
Thêm exhaustiveness check để khi thêm nhánh mới mà quên xử lý thì compiler báo ngay:
ts
function assertNever(x: never): never {
throw new Error('Unhandled state: ' + JSON.stringify(x))
}Gọi assertNever(s) ở default — thêm 'refetching' vào union mà chưa xử lý sẽ lỗi biên dịch.