In most programs, we must make decisions based on input. TypeScript is no exception, using conditional types can describe the relationship between input types and output types.
This article is first synchronously in the 1615a8a5fcb92d personal blog , welcome to subscribe and exchange.
Extends used for conditional judgment
When extends is used to indicate conditional judgment, the following rules can be summarized
- If the types on both sides of extends are the same, then extends
===
. 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 (ie narrow type extends broad type), the result is true, otherwise it is false. You can refer to the following examples:
type result3 = string extends string | number ? true : false // true
- When extends acts on an object, the more keys specified in the object, the narrower the scope of its type definition. You can refer to the following examples:
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 for extends when the conditional , known 'a' | 'b' | 'c' extends 'a'
is false, so Demo<'a' | 'b' | 'c', 'a'>
result is 'a' | 'b' | 'c'
it?
Check official website , which mentions:
When conditional types act on a generic type, they become distributive when given a union type.
That is, when the conditional 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'
. In pseudo code, it is similar to:
function Demo(T, U) {
return T.map(val => {
if (val !== U) return val
return 'never'
})
}
Demo(['a', 'b', 'c'], 'a') // ['never', 'b', 'c']
In addition, according to never type -never type can be assigned to every type, but no type can be assigned to never (except never itself). That is, never | 'b' | 'c'
equivalent to 'b' | 'c'
.
Therefore, the result of Demo<'a' | 'b' | 'c', 'a'>
'a' | 'b' | 'c'
but 'b' | 'c'
.
Tool type
Careful readers may have discovered that the declaration process of the Demo type is actually Exclude<Type, ExcludedUnion>
in the tool type officially provided by TypeScript, which is used to exclude the union type ExcludedUnion from the Type type.
type T = Demo<'a' | 'b' | 'c', 'a'> // T: 'b' | 'c'
Based on the definition of the Demo type, Omit<Type, Keys>
in the official tool type can be further implemented, which is used to remove the object Type
The attribute values in the keys that meet the type of keys.
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 pod
If you want Demo<'a' | 'b' | 'c', 'a'>
result is 'a' | 'b' | 'c'
? Whether it can be achieved according to official website description:
Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.
If you don't want to traverse every type in a generic, you can use square brackets to enclose the generic to indicate that the whole part of the generic is used.
type Demo<T, U> = [T] extends [U] ? never : T
// result 此时类型为 'a' | 'b' | 'c'
type result = Demo<'a' | 'b' | 'c', 'a'>
Use conditional types in arrow functions
When using a ternary expression in an arrow function, the reading habit from left to right leads to confusion for the user if the function content area does not include parentheses. For example, in the code below, is x a function type or a boolean type?
// The intent is not clear.
var x = a => 1 ? true : false
In the eslint rule no-confusing-arrow , the following is recommended:
var x = a => (1 ? true : false)
In TypeScript's type definition, the same is true if you use extends in the arrow function. Due to the reading habit from left to right, the reader will also 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 add parentheses when using extends in the arrow function, 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)
Combining type inference to use condition type
In TypeScript, the type deduction infer syntax is generally used in conjunction with extends. Use it to achieve the purpose of automatically deriving types. For example, use it to implement the tool type ReturnType<Type>
, which is used to return the return type of the function Type.
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>
, 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 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 current community:
embodiment a : Reference Issue .
export type Equal1<T, S> =
[T] extends [S] ? (
[S] extends [T] ? true : false
) : false
The only disadvantage of the current scheme is that the any type is judged to be equal to any other type.
type T = Equal1<{x:any}, {x:number}> // T: true
Solution Two : Refer to issue .
export type Equal2<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<U>() => U extends Y ? 1 : 2) ? true : false
The only shortcoming of the current scheme is that there is a flaw in the handling of the cross type.
type T = Equal2<{x:1} & {y:2}, {x:1, y:2}> // false
Different people have different opinions on the above two methods of judging that the types are equal.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。