In most programs, we have to make decisions based on input. TypeScript is no exception, using conditional types can describe the relationship between input type and output type.
Extends used for conditional judgment
When extends is used to represent conditional judgment, the following rules can be summarized
If the types on both sides of extends are the same, extends can be understood semantically as ===. You can refer to the following example:
type result1 = 'a' extends 'abc' ? true : false // false type result2 = 123 extends 1 ? true : false // false
If the type on the right side of extends contains the type on the left side of extends (i.e., the narrow type extends broad type), the result is true, otherwise false. You can refer to the following example:
type result3 = string extends string | number ? true : false // true
When extends acts on an object, the more keys are specified in the object, the narrower the scope of its type definition. You can refer to the following example:
type result4 = { a: true, b: false } extends { a: true } ? true : false // true
Use conditional types in generic types
Consider the following Demo type definition:
type Demo<T, U> = T extends U ? never : T
Combined with the extends used in conditional judgment, we can see that 'a' | 'b' | 'c' extends 'a' is false, so Demo<'a' | 'b' | 'c', 'a'> The result is 'a' | 'b' | 'c'?
ViewOfficial website, which mentioned:
When conditional types act on a generic type, they become distributive when given a union type.
That is, when the condition type acts on the generic type, the union type will be split and used. That is, Demo<'a' | 'b' | 'c', 'a'> will be split into 'a' extends 'a', 'b' extends 'a', 'c' extends 'a'. Use pseudo-code to represent something similar to:
function Demo(T, U) { return (val => { if (val !== U) return val return 'never' }) } Demo(['a', 'b', 'c'], 'a') // ['never', 'b', 'c']
In addition, according tonever typeDefinition of ― The never type can be assigned to each type, but no type can be assigned to never (except never itself). That is, never | 'b' | 'c' is equivalent to 'b' | 'c'.
So the result of Demo<'a' | 'b' | 'c', 'a'> is not 'a' | 'b' | 'c' but 'b' | 'c'.
Tool Type
Careful readers may have discovered that the declaration process of the Demo type is actually among the tool types provided by TypeScript.Exclude
type T = Demo<'a' | 'b' | 'c', 'a'> // T: 'b' | 'c'
Based on the Demo type definition, it can further implement Omit<Type, Keys> in the official tool type, which is used to remove object Type
The attribute value that satisfies the keys type.
type Omit<Type, Keys> = { [P in Demo<keyof Type, Keys>]: Type<P> } interface Todo { title: string; description: string; completed: boolean; } type T = Omit<Todo, 'description'> // T: { title: string; completed: boolean }
Escape cabin
If you want the result of Demo<'a' | 'b' | 'c', 'a'> is 'a' | 'b' | 'c'? According toOfficial websitedescribe:
Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.
If you do not want to iterate through each type in a generic, you can wrap the generic in square brackets to indicate the entire part of using the generic.
type Demo<T, U> = [T] extends [U] ? never : T
type Demo<T, U> = [T] extends [U] ? never : T // result At this time, the type is 'a' | 'b' | 'c'type result = Demo<'a' | 'b' | 'c', 'a'>
Use conditional types in arrow functions
When using ternary expressions in arrow functions, the left-to-right reading habit causes the function content area to be left unpacked, which will confuse the user. For example, is x in the code below a function type or a boolean type?
// The intent is not clear. var x = a => 1 ? true : false
In eslint rulesno-confusing-arrow In the following way of writing:
var x = a => (1 ? true : false)
In TypeScript's type definition, the same is true for using extends in arrow functions. Due to the left-to-right reading habit, readers will be confused about the execution order of the type code.
type Curry<P extends any[], R> = (arg: Head<P>) => HasTail<P> extends true ? Curry<Tail<P>, R> : R
Therefore, it is recommended to use extends in arrow functions to add brackets, which is very helpful for code review.
type Curry<P extends any[], R> = (arg: Head<P>) => (HasTail<P> extends true ? Curry<Tail<P>, R> : R)
Deduce the use of condition types in combination with type
In TypeScript, type derivation is generally used in combination with extendsinfer grammar. Use it to achieve the purpose of automatically deriving types. For example, use it to implement tool typesReturnType
type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never
type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never MyReturnType<() => string> // string MyReturnType<() => Promise<boolean> // Promise<boolean>
Combining extends and type derivation can also implement array-related Pop<T>, Shift<T>, and Reverse<T> tool types.
Pop<T>:
type Pop<T extends any[]> = T extends [...infer ExceptLast, any] ? ExceptLast : never type T = Pop<[3, 2, 1]> // T: [3, 2]
Shift<T>:
type Shift<T extends any[]> = T extends [infer _, ...infer O] ? O : never type T = Shift<[3, 2, 1]> // T: [2, 1]
Reverse<T>
type Reverse<T> = T extends [infer F, ...infer Others] ? [...Reverse<Others>, F] : [] type T = Reverse<['a', 'b']> // T: ['b', 'a']
Use conditional types to determine that the two types are exactly equal
We can also use conditional types to determine whether the two types A and B are completely equal. There are two main solutions in the community:
Plan 1: Referenceissue。
export type Equal1<T, S> = [T] extends [S] ? ( [S] extends [T] ? true : false ) : false
The only disadvantage of the current solution is that it will judge the any type to be equal to any other type.
type T = Equal1<{x:any}, {x:number}> // T: true
Plan 2: Referenceissue。
export type Equal2<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<U>() => U extends Y ? 1 : 2) ? true : false
The only disadvantage of the current solution is that it has a little flaw in handling the cross type.
type T = Equal2<{x:1} & {y:2}, {x:1, y:2}> // false
The above two methods of judging the same type are different, and I will give you some advice here.
Summarize
This is the article about the intensive reading and practice of condition types in TypeScript. For more related TypeScript content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!