Hello, my name is Xiaoyu Xiaoyu and I’m dedicated to sharing interesting, practical technical articles.

Content is divided into translation and original, if you have questions, please feel free to comment or private message, hope to progress with everyone.

Sharing is not easy, hope to get everyone’s support and attention.

extends

Typescript 2.8 introduces the conditional keyword extends, which looks like this:

T extends U ? X : Y
Copy the code

Doesn’t it look a bit like the ternary operator: condition? Result (1) : result(2) : result(1) : result(2)

If T contains types that are a ‘subset’ of the types that U contains, then take the result X, otherwise take the result Y.

Here are a few more examples of ts predefined condition types for further understanding:

type NonNullable<T> = T extends null | undefined ? never : T;

// If the generic argument T is null or undefined, then never; otherwise, return T directly.
let demo1: NonNullable<number>; // => number
let demo2: NonNullable<string>; // => string
let demo3: NonNullable<undefined | null>; // => never
Copy the code

Distribution type extends

T extends U ? X : Y
Copy the code

In fact, when the above T is of the union type, it will be split, similar to the factorization in mathematics:

(a + b) * c => ac + bc

Here’s another example of a website:

type Diff<T, U> = T extends U ? never : T; // Find the difference set of T
type Filter<T, U> = T extends U ? T : never; // Find the intersection

type T30 = Diff<"a" | "b" | "c" | "d"."a" | "c" | "f">;  // => "b" | "d"
// <"a" | "b" | "c" | "d", "a" | "c" | "f">
/ / equivalent to
// <'a', "a" | "c" | "f"> |
// <'b', "a" | "c" | "f"> |
// <'c', "a" | "c" | "f"> |
// <'d', "a" | "c" | "f">
type T31 = Filter<"a" | "b" | "c" | "d"."a" | "c" | "f">;  // => "a" | "c"
/ / < "a" | "b" | | "c" "d", "a" | "c" | "f" >

let demo1: Diff<number.string>; // => number
Copy the code

And infer.

infer

The extends statement also supports the infer keyword, which allows you to infer a type variable, enabling efficient pattern matching of types. However, this type variable can only be used in the true branch.

/ / built-in ReturnType
type ReturnType<T> = T extends(... args:any[]) => infer R ? R : any;
Copy the code

I don’t know if the friends who are learning TS are confused after reading this introduction, anyway, BEFORE I was…

In fact, after understanding is very simple, here directly say my understanding, should be easy to understand:

Infer X is like declaring a variable that can be used later. Is it a bit like declaring a for loop?

for (let i = 0, len = arr.length; i < len; i++) {
    // do something
}
Copy the code

The difference is that infer X should have a written type variable at this position, but infer R is used instead, which is more flexible.

Note that the infer declaration can only be used in the true branch

Let me give you a few examples to help you understand more:

Example a

// Interpret: if the generic variable T is a subset of () => infer R, return the function return value obtained by infer. Otherwise, return Boolean
type Func<T> = T extends () => infer R ? R : boolean;

let func1: Func<number>; // => boolean
let func2: Func<' '>; // => boolean
let func3: Func<(a)= > Promise<number> >;// => Promise<number>
Copy the code

Example 2

// If a and b are different types, return the union type of different types
type Obj<T> = T extends {a: infer VType, b: infer VType} ? VType : number;

let obj1: Obj<string>; // => number
let obj2: Obj<true>; // => number
let obj3: Obj<{a: number, b: number} >.// => number
let obj4: Obj<{a: number, b: (a)= > void} >.// => number | () => void
Copy the code

Example 3 (UnwrapRef in Vue3)

// If the generic variable T is a 'subset' of ComputedRef, then use UnwrapRefSimple to process the ComputedRef generic parameter V that infer refers to
// If not, UnwrapRefSimple
export type UnwrapRef<T> = T extends ComputedRef<infer V>
  ? UnwrapRefSimple<V>
  : T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>
    
// I am the dividing line
    
/ / if T for Function | CollectionTypes | BaseTypes | 'subset of one of the Ref, returned directly.
// Call UnwrappedObject if it is not a subset of the array. If it is not an object, call UnwrappedObject
type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
  ? T
  : T extends Array<any>? T : Textends object ? UnwrappedObject<T> : T

// I am the dividing line
UnwrapRef (UnwrapRef, UnwrapRef)
type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

// I am the dividing line
    
/ / generic Ref
export interface Ref<T = any> {
  [Symbol()]: true
  value: T
}

// I am the dividing line

export interface ComputedRef<T = any> extends WritableComputedRef<T> {
  readonly value: T
}

// I am the dividing line

export interface WritableComputedRef<T> extends Ref<T> {
  readonly effect: ReactiveEffect<T>
}
Copy the code

I suggest you go through it yourself.

conclusion

The extends and infer provided by TS greatly increase the flexibility and reusability of type judgment. Although it can be used or not used, the skillful use of advanced features will greatly improve the efficiency of TS inference and the readability of code types.

If you have any questions, please point them out.

Happy Labor Day!