Author: Dong Mo

Preface: When writing typescript applications, there are times when we want to reuse or construct types of a particular structure that are hard to express only from typescript with built-in types, interfaces, and classes. At this point we need to use type inference. To discuss type inference, we need to use generics and #infer. In this article we will only discuss generics


In the previous


The generic


Formally, generics in typescript are like generics in most languages (except for the as-yet-unimplemented Go :P) :


// one constrain for array-like object
interface ArrayLike<T> {
    readonly length: number;
    readonly [n: number]: T;
}Copy the code


ArrayLike above represents a class array structure, which represents objects that have the following characteristics:


  1. A read-onlylengthField;
  2. The other fields must be numbers, and each field must correspond to a value of type T, where T is the generic flag


Array-like objects are ancient objects in Javascript, such as function (…) {… } function, whose arguments variables inside are typical array-like objects.


Generics are often used in these scenarios:


  • Reserved words interface
  • Reserved word type
  • Class in the namespace (#namespace) (which is actually an interface at this point)
  • Class at runtime
  • Function definitions (including class constructors)


Let’s show in turn how type inference can be powerful in each of these scenarios.


Type derivation based on generics


When using generics for type inference, remember:


  1. Generics only serve static analysis of types, not Javascript runtime
  2. Logical extrapolation when thinking about type derivation should not be “what type will it get at run time “, but” what type will it get based on the properties of the type itself”


Point 2 May be a little hard to understand, but we’ll skip it and see what it means in the next passage.


Interface: T and keyof


Full key selectable


As mentioned in the previous layer, we can use keyof to extract all the key names of an interface. When generics are introduced, keyof can do even more interesting things:


/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P inkeyof T]? : T[P]; };Copy the code


Partial is for T(T needs to be of a type that can be treated as an interface), find all the key names, and rely on the key names to get a new interface that has the same structure but all the keys are optional.


For example, with forms, UninitForm can be an all-key optional version of it:


interface Form {
    name: string
    age: number
    sex: 'male' | 'female' | 'other'
}

type UninitForm = Partial<Form>Copy the code


UninitForm is equivalent to:


{ name? : string; age? : number; sex? :"male" | "female" | "other";
}Copy the code


Full keys are required


In contrast, if you already have UninitForm, you can rely on Required to reach its all-key Required version:


/**
 * Make all properties in T required
 */
type Required<T> = {
    [P inkeyof T]-? : T[P]; };Copy the code


interface UninitForm { name? : string; age? : number; sex? :"male" | "female" | "other";
}

type AllKeyRequriedForm = Required<UninitForm>Copy the code


Full key read-only


With readonly, we can make all keys in an interface readonly


/**
 * Make all properties in T readonly* /type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};Copy the code


Powerful extends ternary derivation


In the case of JS, extends is a reserved word for an extension class; In typescript, extends means type derivation when it appears in the following scenario:


/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;Copy the code


Extends [DEP]? [RESULT1] : an expression for [RESULT2] is a typescript type derivation that follows the following rules:


If the generic T must satisfy the constraint of [DEP] (that is, T extends [DEP] is true), then the expression result is [RESULT1]. Conversely, the expression result is [RESULT2]:


  1. When [DEP] is a primitive type, if T is the corresponding primitive type, thenT extends [DEP]trueAnd vice versa forfalse
  2. When [DEP] is interface/class, if T must satisfy its constraints, thenT extends [DEP]trueAnd vice versa forfalse
  3. When [DEP] is void/never, the basic type is handled
  4. When [DEP] is a union type, the types that make up [DEP] are substituted into T, and the final result is the union type of the results of those operations
  5. If [DEP] is anyT extends [DEP]Constant for thetrue


Following these rules, let’s analyze the Exclude.


/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;Copy the code


Analysis shows that:


  • ExcludeThere are two necessary generic tags inTU(Because none of them provide default generics)
  • If T is a union type, then we get all types of T except U


So let’s apply Exclude


type NoOne = Exclude<1 | 2 | 3, 1>  // NoOne = 2 | 3Copy the code


Also, to illustrate point 4 above, we can write a useless NotRealExclude;


type NotRealExclude<T, U> = T extends U ? U : T;

type Orig = NotRealExclude<1 | 2 | 3, 1> // Orig = 1 | 2 | 3Copy the code


Since NotRealExclude returns U if T does not conform to U and returns T if T conforms to U, the net result is that all the types that make up T are reassembled.


Extends a stew


Now that you know the basic use of extends, let’s look at some more examples:


Select, exclude, and recombine the keys of an object


/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};Copy the code


Just as our usual pick function can extract a specific key-value pair from an object, pick can also extract a specific key-value definition from T.


interface Person { name: string age: number sex? :"male" | "female" | "other";
}

/**
 * equivalent to { name: string }
 */
type SimplePersonInfo = Pick<Person, 'name'>Copy the code


If you can Pick, you can Omit anything


/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;Copy the code


/**
 * equivalent to { name: string, age: number }
 */
type SimplePersonInfo = Omit<Person, 'sex'>Copy the code


We know that in the index of an object, the in keyword can be used to extract the type from the union type as the key name of the interface, for example:


type Ks = 'a' | 'b' | 'c'
type KObject = {
    [P in Ks]: any
}Copy the code


KObject can contain key names of ‘a’, ‘b’, and ‘c’.


With extends, we can easily build a dictionary of values of the same type from an interface, which is Record:


/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};Copy the code


// construct one family with 3 keys: parent, mom, child
type Family = Record<'parent' | 'mom' | 'child', Person>Copy the code


This is equivalent to


interface Family {
    parent: Person,
    mom: Person,
    child: Person
}Copy the code


Non-nullated variables, etc


/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;Copy the code


conclusion


At this point,


  1. We know how to use generics to make new types, right
  2. We know how to combine generics with the extends triplet and index in expression to disassemble and reassemble an interface


So far, however, we have only dealt with non-function interface(class)/type scenarios. Can we do something special for function interfaces, such as extracting the type of the second argument from an existing function definition? For the following func, can we extract the type of arg2?


interface func () {
    (arg1: string, arg2: {
        bar: string
    }): void
}Copy the code


In the next article, we’ll discuss how to do this using the infer keyword.


other


Type inference based on generics is theoretically possible starting with typescript 2.8 (actually earlier, but typescript 2.8/3.5 is a landmark release, so it’s classified as such), starting with typescript 3.5, There are some official built-in types (#type) and interfaces (#interface) for derivation, and these are good examples for us to learn about type derivation. All examples used in this article come from typescript’s built-in lib.es5.d.ts.


Note that for generics, T is often used as the first choice for the generic tag; T is to generics what Foo is to sample code