“Don’t be afraid, don’t settle, work hard in the future” — Hello! I am little sesame 😄

1. Cross types&

The & operator allows you to superimpose existing types into a single type that contains all of the required characteristics of the type.

  • Cross typing is merging multiple types into one type. A cross type is simply a union of attributes of two interface types
type PersonName = { name: string }
type Person = PersonName & { age: number }

let person: Person = {
  name: 'Little Golden Sesame'.age: 18
}
Copy the code

In the above code we define the PersonName type, then use the & operator to create a new Person type representing a type containing name and age, and then define and initialize a variable of type Person.

  • In the process of merging multiple types, it happens that some types have the same members, but the corresponding types are inconsistent
interface X {
  c: string;
  d: string;
}

interface Y {
  c: number;
  e: string
}

type XY = X & Y;
type YX = Y & X;

let p: XY = { c: 6.d: "d".e: "e" }; // ERROR cannot assign type "number" to type "never".
let q: YX = { c: "c".d: "d".e: "e" }; // ERROR cannot assign type "string" to type "never".
Copy the code

In the code above, interfaces X and Y both have the same member C, but they are of different types. In this case, the type of member C of XY or YX is string & number, that is, the type of member C is both string and number. Obviously this type does not exist, so member C is of type never. [2]


2. Association type| 

Joint type using | separated for each type.

  • Union Types indicate that the value can be one of many Types
  • When unassigned, only properties and methods that are common to both types can be accessed on the union type
let name: string | number
name = 3
name = 'Little Golden Sesame'
Copy the code

Here let name: string | number means, allow the type of the name is a string or number, but not other types.

name = true / / the ERROR type "true" can not be assigned to a type of "string | number".
Copy the code

Access a property or method of the union type

When TypeScript doesn’t know what type a variable of a union type is, we can only access properties or methods that are common to all types of the union type:

function getLength(something: string | number) :number {
    return something.length; // ERROR
}
Copy the code

In the case,lengthnotstring 和 numberThe common property of the.

There is no problem accessing the common attributes of string and number:

function getString(something: string | number) :string {
    return something.toString();
}
Copy the code

When a variable of the union type is assigned, it can infer a type according to the rules of type inference:

let name: string | number
name = 3
console.log(name.length) // The attribute "length" does not exist on ERROR type "number".
name = 'Little Golden Sesame'
console.log(name.length) / / 5
Copy the code

In the example above, the name in the second line is inferred to be number, and an error is reported when accessing its length attribute.

The name of the fourth line is inferred to be a string, and accessing its length attribute is not an error.

3. Literal types

  • You can combine strings, numbers, and Boolean literals into a union type
type ZType = 1 | 'one' | true
let t1: ZType = 1
let t2: ZType = 'one'
let t3: ZType = true

// The literal type
let Gender3: 'BOY' | 'GIRL'
Gender3 = 'BOY' // It can compile
Gender3 = 'GIRL' // It can compile
Gender3 = true // Failed to compile
Copy the code

3.1 String literal types

The string literal type is used to constrain the value to one of several strings.

type Direction = 'North' | 'East' | 'South' | 'West'; 
function move(distance: number, direction: Direction) { 
    // ... 
}

move(1.'North');  // YES
move(1.'Little Sesame'); // ERROR, parameters of type "little sesame" cannot be assigned to parameters of type "Direction".
Copy the code

In the example above, we use type to specify a string literal type Direction, which can only take one of the four strings.

Note that both type aliases and string literal types are defined using type.

String literals VS union types

  • The string literal type is used to restrict the value to aSeveral stringsWhere the union type indicates that the value can beA variety of typesOne of the
  • String literals limit where they are used to accept only certain values. Union types are not limited to values, only the types of qualified values need to be consistent

3.2 Numeric literal types

Similar to string literals

type Direction = 11 | 12 | 13
function move(distance: number, direction: Direction) { 
    // ... 
}

move(1.11);  // YES
move(1.1); // ERROR, parameters of type 1 cannot be assigned to parameters of type Direction.
Copy the code

4. Index typekeyof

  • Using index types, the compiler can examine code that uses dynamic attribute names.

As follows, take the values of some properties from the object to form a new array.

let obj = {
  a: 1.b: 2.c: 3
}

function getValues(obj: any, keys: string[]) {
  return keys.map(key= > obj[key])
}

console.log(getValues(obj, ['a'.'b'])) // [1, 2]

console.log(getValues(obj, ['a'.'f'])) // [ 1, undefined ]
Copy the code

In the above example, we want to throw an exception if the value of obj does not exist, so we want to throw an exception if the value of obj does not exist.

It can be accomplished mainly by the following three points:

  • Query operators for index types:keyof T(Union type representing literals of all common properties of type T)
  • Index access operators:T[K]
  • Generic constraints:T extends U

Let’s begin by modifying the getValues function based on the above conditions

  • First we need a genericTIt represents the parameter passed inobjBecause we cannot determine the parameters when we write the codeobjWhat exactly is the type of, so get in this caseobjMust use the future-oriented type – generics.
function getValues< T> (obj: T, keys: string[]) {
  return keys.map(key= > obj[key])
}
Copy the code
  • So the second argument passed inkeys, which features an array whose members must be defined by parametersobjProperty name, at which point we can easily think of the operator we just learnedkeyof.keyof TOn behalf of the parameterobjType of the joint type of the attribute name of our parameterkeysMember type ofKYou just have to constrain tokeyof TCan.
function getValues<T.K extends keyof T> (obj: T, keys: K[]) {
  return keys.map(key= > obj[key])
}
Copy the code
  • The return value is, we pass the type accessorT[K]I can get the type of the corresponding property values, their arrayT[K][]Is the type of the return value.
function getValues<T.K extends keyof T> (obj: T, keys: K[]) :T[K] []{
  return keys.map(key= > obj[key])
}
Copy the code

At this point our function is completely reinvented, so let’s try printing:

console.log(getValues(obj, ['a'.'b'])) // [1, 2]
console.log(getValues(obj, ['a'.'f'])) / / the ERROR is not able to type "" f" assigned to "type" a "|" "|" b "" c".
Copy the code

The getValues function in TypeScript, which uses index types with type operators, not only provides tighter type constraints, but also provides stronger code hints.


5. Mapping typein

A common task is to make each attribute of a known type optional:

interface Person {
  name: string
  age: number
  gender: 'male' | 'female'
}
Copy the code

[K in Keys] [K in Keys] [K in Keys] [K in Keys] [K in Keys]

  • K: Type variable, bound to each property in turn, corresponding to the type of each property name
  • Keys: an associative type of string literals representing a set of attribute names

So how do we do that?

  • First, we need to find Keys, the associative type of string literals, using the keyof operator mentioned above. We pass in type Person and get keyof Person, the associative type of the attribute name of type Person.

  • Then we need to map the keyof Person attribute names one by one [key in keyof Person]. If we want to change all the attribute members to optional types, we need Person[key] to fetch the corresponding attribute values. Finally, we will regenerate an optional new type {[key in keyof Person]? : the Person [key]}.

Denoted by a type alias is:

type PartPerson = {
  [key inkeyof Person]? : Person[key] }let p1: PartPerson = {}
// Generics can also be used
type Part<T> = {
  [key inkeyof T]? : T[key] }let p2: Part<Person> = {}
Copy the code

Indeed, all attributes have become optional types

5.1 Mapping Types of Built-in Tools

  • TS has some built-in utility types to help us use the type system better (see how this works in lib.es5.d.ts)
interface obj {
  a: string;
  b: number;
  c: boolean;
}
Copy the code

5.1.1 Partial

  • Partial

    returns a Partial

    that changes an attribute passed from non-optional to optional:

type PartialObj = Partial<obj>
Copy the code

5.1.2 Required

  • Required

    Returns the attribute passed as Required:
type RequiredObj = Required<PartialObj>
Copy the code

5.1.3 Readonly

  • Readonly

    Can make an incoming property read-only:
type ReadonlyObj = Readonly<obj>
Copy the code

5.1.4 ensuring a Pick

  • Pick

    helps us return an item from an attribute passed in
    ,>
type PickObj = Pick<obj, 'a' | 'b'>
// Retrieve a and b attributes from obj
Copy the code

interface Animal {
  name: string
  age: number
}
// Pick the name attribute from Animal
type AnimalSub = Pick<Animal, 'name'> // {name: string}
let a: AnimalSub = { name: 'Little Golden Sesame' }
Copy the code

5.1.5 Record

  • Record<T, U> will string literal typeTFor all string variables of the new typeKeyValue,UType asKeyThe type of
type RecordObj = Record<'x' | 'y', obj>
Copy the code

5.2 Control of mapping type modifiers

  • TypeScript 2.8 adds control over mapping type modifiers
  • To be specific, onereadonly?Modifiers can be prefixed in a mapping type+or-To indicate that the modifier should be added or removed
  • Some of the built-in tool types in TS take advantage of this feature (Partial, Required, Readonly… Here we can refer to the Partial and Required implementations
type MutableRequired<T> = { -readonly [P inkeyof T]-? : T[P] };// Remove readonly and?
type ReadonlyPartial<T> = { +readonly [P inkeyof T]+? : T[P] };// Add readonly and?
Copy the code

A modifier without a + or – prefix has the same effect as a modifier with a + prefix. So the ReadonlyPartial

type above is the same as the one below

type ReadonlyPartial<T> = { readonly [P inkeyof T]? : T[P] };// Add readonly and?
Copy the code

6. Condition types

TypeScript 2.8 introduces conditional types, which can represent non-uniform types. A condition type uses a condition expression for type relationship detection to choose between two types:

T extends U ? X : Y
Copy the code

The above type means that the type is X if T can be assigned to U, and Y otherwise. Similar to the ternary conditional operator in JavaScript.

6.1 Distributed Condition Types

When the type examined within a Conditional type is naked Type parameter, it is called a distributed Conditional type. It is special in the joint type, it can automatically take A simple example, assuming that the T type is A | B | C, then it will be parsed into three conditions branch, as shown below.

A|B|C extends U ? X : Y 
/ / equivalent to the
A extends U ? X : Y | B extends U ? X : Y | C extends U ? X : Y
Copy the code

No naked type parameter

If T or U contain type variables, parsing is delayed, that is, until the type variables all have concrete types before the result of the conditional type can be computed. In the following example, a Person interface is created, the return type of the declared global function add() changes depending on whether it is a subtype of Person, and the add() function is called in the generic function func().

interface Person {
  name: string;
  age: number;
  getName(): string;
}
declare function add\ <T> (x: T) :T extends Person ? string : number;
function func\ <U> (x: U) {
  let a = add(x);
  let b: string | number = a;
}
Copy the code

Although the type of variable A is uncertain, the result of the condition type is either string or number, so it can be successfully assigned to variable B.

  • Distributed conditional types can be used to Filter union types, as shown below. The Filter

    types remove subtypes of U from T.
    ,>
type Filter<T, U> = T extends U ? never : T;
type T1 = Filter<"a" | "b" | "c" | "d"."a" | "c" | "f">; // "b" | "d"
type T2 = Filter<string | number | (() = > void), Function>; // string | number
Copy the code
  • Distributed condition types can also be used together with mapping types for specific type mapping. That is, different source types correspond to different mapping rules, such as method names of mapping interfaces, as shown in the following figure.
interface Person {
  name: string;
  age: number;
  getName(): string;
}

type FunctionPropertyNames<T> = { 
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T];
type T3 = FunctionPropertyNames<Person>; // "getName"
Copy the code

6.2 Type Inferenceinfer

In the extends clause of conditional types, it is possible to introduce a type variable to be inferred through the Infer statement, and multiple Infer statements of the same type can occur, such as using the Infer statement to extract the return value type of a function, as shown below. It is important to note that type variables declared by Infer can only be used in the True branch.

type Func<T> = T extends(... args:any[]) => infer R ? R : any;
Copy the code
  • When a function has an overload, the last function signature is inferred, as shown below, where ReturnType

    is the built-in conditional type to get the return value type of function type T.
declare function load(x: string) :number;
declare function load(x: number) :string;
declare function load(x: string | number) :string | number;
type T4 = ReturnType<typeof load>; // string | number
Copy the code
  • Note that the infer statement cannot be used in constraint substatements of normal type parameters, as shown below.
type Func<T extends(... args:any[]) => infer R> = R; // Error, not supported
Copy the code
  • However, you can remove the type variable from the constraint and transfer it to the condition type to achieve the same effect, as shown below.
type AnyFunction = (. args:any[]) = > any;
type Func<T extends AnyFunction> = T extends(... args:any[]) => infer R ? R : any;
Copy the code

6.3 Predefined Condition types

TypeScript 2.8 adds predefined conditional types in lib.d.ts (see lib.es5.d.ts;). :

  • Exclude

    : Exclude the subtype of U from T.
    ,>
  • 2) Extract

    : Extract subtypes of U from T
    ,>
  • 3) NonNullable

    : Remove null and undefined from T.
  • 4) ReturnType

    : Get the return value type of the function.
  • 5) InstanceType

    : get the constructor InstanceType.

6.3.1 Exclude

  • Exclude U from the types to which T can be assigned
type E = Exclude<string | number.string>
let e: E = 10 // number
Copy the code

6.3.2 Extract

  • Extract U from the type T can be assigned to
type E = Extract<string | number.string>
let e: E = '1' // string
Copy the code

6.3.3 NonNullable

  • Exclude null and undefined from T
type E = NonNullable<string | number | null | undefined>
let e: E = '1' // string | number
Copy the code

6.3.4 ReturnType

  • Gets the return type of the function type
function getUserInfo() {
  return { name: 'Little Golden Sesame'.age: 10}}type UserInfo = ReturnType<typeof getUserInfo>
let user: UserInfo = { name: 'Little Golden Sesame'.age: 10 } // {name: string; age: number; }
Copy the code

6.3.5 InstanceType

  • Gets the instance type of the constructor
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}
type P = InstanceType<typeof Person>
let p: P = new Person('1') // Person
Copy the code

reference

[1]. TypeScript Chinese

[2]. A rare TS Study Guide (1.8W word)

[3].typescript (6) — advanced types