Key words:

  • Keyof, typeof
  • T extends any ? U : Y
  • infer
  • Covariant, anti-variant, bivariant, invariant (the last one is not mentioned in this paper, but is available in the reference article)

Show you code

If the following can be understood and understood, then congratulations, you do not need to continue reading this article, see you next time 😄 ~~

Type MyPartial<T> = {[U in keyof T]? : T[U]}; type MyReadonly<T> = {readonly [U in keyof T]: T[U]}; type MyRecord<K extends keyof any, V> = { [Key in K]: V} type MyPick<Type, Keys extends keyof Type> = {[K in Keys]: Type[K]}; type MyExtract<Type, Union> = Union extends Type ? Union : never; type MyNonNullable<Type> = Type extends void ? never : Type; type MyParameters<Type extends (... args: any[]) => any> = Type extends (... args: infer I) => any ? (I) : never; type MyConstructorParameters<Type> = Type extends new (... args: infer I) => any ? (I) : never; type MyReturnType<Func> = Func extends (... args: any[]) => infer R ? R : never; PromiseInnerType<T extends Promise<any>> = T extends Promise<infer P>? P : Never // string type Test = PromiseInnerType<Promise<string>> // Type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never // { a: string } | { b: number } => { a: string } & { b: number } type Test = UnionToIntersection<{ a: string } | { b: number }> type Prettify<T> = T extends infer U ? { [K in keyof U]: U[K] } : never // { a: string; b: number; c: boolean } type Param = Prettify<{ a: string } & { b: number } & { c: boolean }>Copy the code

If there are one or two that you don’t understand, then please click on the table of contents to quickly enter the ~ that you want to see

Keyof, typeof

These two keywords are more basic, two kinds of posture is simple to understand:

  1. Keyof official website introduction, Typeof official website introduction
  2. Listen to me for a minute

keyof

Gets the set of types for all keys of type T.

type Point = { x: number; y: number }; type P = keyof Point; / / ^ x | y Point only two key type Mapish = {[k: string] : Boolean}; type M = keyof Mapish; / / ^ number | string is what? type KeyOfAny = keyof any; / / ^ string | number | symbol?Copy the code

Mapish and there is no number, why is number M | string? Because in JS:

const obj = {}; // You can count access obj[0]; // obj['0']Copy the code

Why KeyOfAny? Keyof itself, as a key to get all the key types of an object, is itself specific to the object. To access an object, you can use these three keys. So, that’s it. You can also refer to this question.

typeof

Note that it must be distinguished from the js built-in Typeof. The typeof in ts is followed by a concrete value that is used to obtain the typeof the value:

let s = "hello";
let n: typeof s;
//  ^string
let f = (name: string) => {
    return 1;
};
type TypeF = typeof f;
//   ^(name: string) => number
Copy the code

Relatively simple, not much shrink ~

extends

Extends extends ~ is not an inheritance of interface or class

The first generic extends was introduced in TS1.8, linked here. This was originally used to restrict generic types.

function assign<T extends U, U>(target: T, source: U): T {
  for (let id in source) {
    target[id] = source[id];
  }
  return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 }); // Error
Copy the code

It’s easy to restrict generic types, but in combination with other things, it gets a little bit tricky. Go on

Conditional Types have been introduced in TS 2.8, so it’s important to read the documentation! This is my assistant translation, table spray translation ability ~~ :

Condition type T extends U? X: Y can be derived directly as X or Y, or it can be delayed because the condition depends on more types. Whether the derivation is direct or delayed depends on:

  1. First, given types T’ and U’, which are instances of types T and U, respectively (replaced by any if T and U have type arguments), if T’ cannot be assigned to U’, then the conditional type is ultimately deduced to Y. Intuitively, if the maximized instances of T can’t be assigned to the maximized instances of U, then we’ll just go straight to Y.

  2. Next, for each type variable introduced by the infer keyword statement in U, a set of candidate types is gathered by infering from T to U (using the same inference algorithm as type inference for generic functions). For a given inferred type V, if any candidate types are inferred from a covariant position, then inferred type V is the union of these candidate types; Otherwise, if any candidate is inferred from the mutable position, inferred type V is the intersection of these candidate types; Otherwise, type V is never.

  3. Then, given an instance of type T T, where all inferred type variables are replaced by the inferred type V from the previous step, then inferred to X if T “” is absolutely assignable to U. Absolute assignment is consistent with conventional assignment except that type variables are not considered. Intuitively, when a type is absolutely assignable to another type, we say it is assignable to all instances of those types.

  4. Finally, conditional types rely on more type variables, so type derivation is delayed.

It’s not easy to understand, is it? Chestnuts are easier to understand. (The concepts of covariant and resistant positions are ignored in the infer section.)

Infer: Infer is infer. Infer is infer. Infer:

type TypeName<T> = T extends string
  ? "string"
  : T extends number
  ? "number"
  : T extends boolean
  ? "boolean"
  : T extends undefined
  ? "undefined"
  : T extends Function
  ? "function"
  : "object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<"a">; // "string"
type T2 = TypeName<true>; // "boolean"
type T3 = TypeName<() => void>; // "function"
type T4 = TypeName<string[]>; // "object"
Copy the code
  • In T0, string extends String is true, so returns ‘string’;

  • In T1, ‘a’ is an instantiation of string, which is T’ in point 1, and U’ is an instantiation of all strings, so ‘a’ must be mated to string, so return ‘string’;

  • In T2, similarly, true is an instantiation of Boolean, So~

  • T3-t4, also too.

So you can follow the distribution condition type and see infer~

Distributive conditional types

The document

I also put this distribution condition type in the extends section.

Conditional types whose types are selected as bare type parameters are called distributed conditional types (that is, not wrapped in such things as arrays, tuples, or functions). Distribution condition types are automatically distributed over union types during instantiation. For example, T extends U? X: Y, A type parameter for A | B | C, T resolved as (A extends U? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

type BoxedValue<T> = { value: T };
type BoxedArray<T> = { array: T[] };
type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;
type T20 = Boxed<string>; // BoxedValue<string>;
type T21 = Boxed<number[]>; // BoxedArray<number>;
type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;
type T23 = Boxed<string> | Boxed<number[]>; // BoxedValue<string> | BoxedArray<number>;
Copy the code

T22 union type, which is equal to split and then union, which is equal to T23.

Note that BoxedArray

is written as an array because T already has a base type of any[]. BoxedArray

; BoxedArray

; However, there are also differences, written in the form of T[0], it is clear that only the type of 0 index; Writing T[number] indicates that a combined type of all types is required. That’s something to be aware of.
[0]>
[0]>
[number]>

Snipe three, if object type:

type Boxed2<T> = T extends Record<string, number> ? BoxedArray<T[string]> : BoxedValue<T>;
type T24 = Boxed2<{[key: string]: number}> // BoxedArray<number>
type T25 = Boxed2<{[key: string]: string}> // BoxedArray<{[key: string]: string}>
Copy the code

infer

Also proposed by TS2.8.

The infer keyword can only be used in the extends statement to indicate a type to be derived. We can infer the same type many times.

For example, the new ReturnType in 2.8:

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

ReturnType gets the ReturnType of a function if T can be assigned to (… Args: any[]) => infer R return type R, otherwise return any. Infer R is returned to represent the type R that needs to be inferred.

Similarly, there is an “infinite extends”, as in:

type Unpacked<T> = T extends (infer U)[] ? U : T extends (... args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T;Copy the code

This article climax is coming, everybody stop Shouting!

Covariant position derivation

The following example illustrates how a joint type can be derived from multiple candidate types of the same type variable in a covariant position. (You’re ugly, so many bold words here, isn’t that important?) Look not to understand? That’s okay. I’ll tell you later.

Again, the official example:

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;
type T10 = Foo<{ a: string; b: string }>; // string
type T11 = Foo<{ a: string; b: number }>; // string | number
Copy the code

Here, Foo determines whether T can be mated to {a: infer U; B: Infer U}, where U is the inference type of key A and B.

A and B in T10 are both strings. Oh yeah, Foo is a string, no problem!

T11 is a string of a and b is number, oh yeah, Foo is directly string and number of joint type (that is, a string | number)! Why? Infer was designed to infer from TS2.8.

A word of caution: object type types can be considered covariant types. (Specific reasons to see the reference article, interested to see O98K)

Derivation of resistant position

How to deduce an intersection type for multiple candidate types of the same type variable in mutable position. (Note the types of associations distinguished from covariant positions)

type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
  ? U
  : never;
type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string
type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string & number
Copy the code

If (x: string) => void; if (x: string) => void;

In T21, the type of X in A is string, and that in B is number. But the parameter type U of infer is the same. So what type should infer be? Oh, no, you shouldn’t ask. It says… But why string & number? Why? Infer was designed to infer from TS2.8.

A caveat: Functional types can be considered variable resistant.

Here’s a related note about the anti-variable types of functions:

StrictFunctionTypes flag bit. This flag was introduced in TS2.6 to change the default bidirectional covariant to anti-covariant by setting this strict bit. That is, the default bidirectional covariant is no longer used for function types. But this does not mean that turning off the flag bit will affect the conclusion above. It does not, because the function is bidirectional by default and is therefore mutation-resistant.

Bidirectional covariance is also referred to in the following references.

UnionToIntersection is easy to understand with the above basic understanding of covariant and anti – variation.

UnionToIntersection

A bit far, copy the code again:

Type UnionToIntersection<U> = (U extends any? (k: U) => void : never) extends ((k: infer I) => void) ? I : never // { a: string } | { b: number } => { a: string } & { b: number } type Test = UnionToIntersection<{ a: string } | { b: number }>Copy the code

If it’s jump straight to it, but don’t know distributed condition, infer and covariance, resistance to change location this a few words, then please click on the directory slightly ~ under study

// Wait 10 minutes and……

Then, we can analyze the utility class:

  1. U extends any ? It must! Anything in TS can be matched to any! So, now this becomes: (k: U) => void extends ((k: infer I) => void)? I : never

  2. Return type = I “= > {a: string} | I ‘ ‘= > {b: number} (note that this is not a distributed condition type, distributed condition type needed is naked)

  3. At this point I is derived into two types! How to deal with it? In step 1, we found that it itself is the type of object type {a: string} | {b: number} into a function parameter type, nothing wrong with you? Deriving a type from a function satisfies a variable position, right? Invariant position derivation, the final type is: yes, you’re right, intersection! So the final return type = I’ => {a: string} &I “=> {b: number} = {a: string} &{b: number} = {a: string; b: number}

The above analysis can match: stackoverflow.com/questions/5… Let’s see.

reference

What is covariance and anti-covariance: what-are-covariance-and contravariance

Ts of covariance, resisting change, double, what is the same: zhuanlan.zhihu.com/p/143054881

Ts: incurable diseases zhuanlan.zhihu.com/p/82459341