Item 53: Know How to Control the Distribution of Unions over Conditional Types
要点
- Think about whether you want unions to distribute over your conditional types.
- Know how to enable or disable distribution by adding conditions or by wrapping conditions in one-tuples.
- Be aware of the surprising behavior of
boolean
andnever
types when they distribute over unions. - 考虑是否希望联合类型在你的条件类型上分发(distribute)。
- 了解如何通过添加条件或将条件包装在单元素元组(one-tuples)中来启用或禁用分发。
- 注意
boolean
和never
类型在分发到联合类型时的意外行为。
正文
ts
declare function double<T extends number | string>(
x: T
): T extends string ? string : number
const num = double(12)
// ^? const num: number
const str = double('x')
// ^? const str: string
declare let numOrStr: number | string
const either = double(numOrStr)
// ^? const either: number | string
ts
type Comparable<T> = T extends Date
? Date | number
: T extends number
? number
: T extends string
? string
: never
declare function isLessThan<T>(a: T, b: Comparable<T>): boolean
ts
isLessThan(new Date(), new Date()) // ok
isLessThan(new Date(), Date.now()) // ok, Date/number comparison allowed
isLessThan(12, 23) // ok
isLessThan('A', 'B') // ok
isLessThan(12, 'B')
// ~~~ Argument of type 'string' is not assignable to parameter
// of type 'number'.
ts
let dateOrStr = Math.random() < 0.5 ? new Date() : 'A'
// ^? let dateOrStr: Date | string
isLessThan(dateOrStr, 'B') // ok, but should be an error
ts
type Comparable<T> = [T] extends [Date]
? Date | number
: [T] extends [number]
? number
: [T] extends [string]
? string
: never
ts
isLessThan(new Date(), new Date()) // ok
isLessThan(new Date(), Date.now()) // ok, Date/number comparison allowed
isLessThan(12, 23) // ok
isLessThan('A', 'B') // ok
isLessThan(12, 'B')
// ~~~ Argument of type 'string' is not assignable to parameter
// of type 'number'.
isLessThan(dateOrStr, 'B')
// ~~~ Argument of type 'string' is not assignable to
// parameter of type 'never'.
ts
type NTuple<T, N extends number> = NTupleHelp<T, N, []>
type NTupleHelp<T, N extends number, Acc extends T[]> = Acc['length'] extends N
? Acc
: NTupleHelp<T, N, [T, ...Acc]>
ts
type PairOfStrings = NTuple<string, 2>
// ^? type PairOfStrings = [string, string]
type TripleOfNumbers = NTuple<number, 3>
// ^? type TripleOfNumbers = [number, number, number]
ts
type PairOrTriple = NTuple<bigint, 2 | 3>
// ^? type PairOrTriple = [bigint, bigint]
ts
type NTuple<T, N extends number> = N extends number
? NTupleHelp<T, N, []>
: never
ts
type PairOrTriple = NTuple<bigint, 2 | 3>
// ^? type PairOrTriple = [bigint, bigint] | [bigint, bigint, bigint]
ts
type CelebrateIfTrue<V> = V extends true ? 'Huzzah!' : never
type Party = CelebrateIfTrue<true>
// ^? type Party = "Huzzah!"
type NoParty = CelebrateIfTrue<false>
// ^? type NoParty = never
type SurpriseParty = CelebrateIfTrue<boolean>
// ^? type SurpriseParty = "Huzzah!"
ts
type CelebrateIfTrue<V> = [V] extends [true] ? 'Huzzah!' : never
type SurpriseParty = CelebrateIfTrue<boolean>
// ^? type SurpriseParty = never
ts
type AllowIn<T> = T extends { password: 'open-sesame' } ? 'Yes' : 'No'
ts
type N = AllowIn<never>
// ^? type N = never