This article appeared in Denver on June 5, 2021

Please like comment collection and forwarding oh!

TS series:

  1. TypeScript advancements, how to avoid any

What are Utility Types

TS built-in utility type for type conversion

Understanding it thoroughly will greatly improve your TS skills

This article explains each built-in Utility Type in terms of implementation, usage, and scenario

Built-in Utility Types

Partial

Makes all attributes of type T passed optional

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

Partial accepts only one generic parameter T,

Keyof is relatively simple to understand. The index type query operator extracts the keyof an index type into a union type, such as

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}
type DogsKey = keyof Dogs / / equivalent type DogsKey = "dogName" | "dogAge" | "dogKind"
Copy the code

The in keyword is the key to understanding this source code. The TS documentation gives a definition: key remapping in mapped types

Its syntax is usually of the following form:

OldType is an associative type
type NewType = { [K in OldType]: NewResultType }
Copy the code

It contains roughly five parts

1. Red area: the type alias used to hold it

2. White area: the type alias K (or other alias) is bound to each attribute of the union type in turn

3. Blue area: in keyword

{[key: string]: {[key: string]: {[key: string]: {[key: string]: {[key: string]: {[key: string]: {[key: string]: {[key: string]: {[key: string] ResultType} is written the same way

5. Pink area: The result type of the property

TS 4.1 above, you can use the AS operator after the orange area to remap the keys in the mapping type, which targets the keys in the white area. In addition to these five sections, the property modifiers readonly and?

If in the above code, OldType for type OldType = “key1” | “key2”, then the NewType equivalent

type NewType = {
  key1: NewResultType
  key2: NewResultType
}
Copy the code

You can see similar examples on the TS website.

In index types, this is written (attribute modifier:?) You can’t do

type IndexType = {
    [key: string]? :string // This is not the case
}
Copy the code

But in the mapping type,? I can write it this way

type MappingType = {
  [key inOldType]? : NewResultType// The correct way to write
}
Copy the code

The code above yields one of these types

typeNewType = { key1? : NewResultType |undefinedkey2? : NewResultType |undefined
}
Copy the code

The source code treats the result type of an attribute like this: T[P], or index access

Index access An index can be used to access its specific type, for example:

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}

type DogName = Dogs["dogName"] // Get a string
Copy the code

If the string “dogName” represents a literal type, the following notation is similar to T[P]

type DogNameKey = "dogName"
type DogName = Dogs[DogNameKey]
Copy the code

For P in the [P in keyof T] section of the source code, the in operator will be one of the concrete literal types in the union type

Whereas T is the original (passed) index type, T[P] accesses the specific type of the P index

  • Application Scenario Example

    1. Object extension operators, such as the implementation of a simple setState based on the useReducer

type State = {
  loading: boolean
  list: Array<any>
  page: number
}
const [state, setState] = useReducer(
  (state: State, nextState: Partial<State>) = > {
    return{... state, ... nextState } }, {loading: false.list: [].page: 0,})/ / use
setState({ page: 1 })
Copy the code

In the above code, nextState is merged with the original state. NextState does not need to contain all keys of type state, so use Partial to define the type

  1. None of the parameters are required, but the parameters are initialized if they are not passed
interface Params {
  param1: string
  param2: number
  param3: Array<string>}function testFunction(params: Partial<Params>) {
  const requiredParams: Params = {
    param1: params.param1 ?? "".param2: params.param2 ?? 0.param3: params.param3 ?? [],}return requiredParams
}
Copy the code

Required

Make all attributes mandatory

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

TS has improved control over mapping type modifiers in version 2.8, mapping modifiers – documents

After this release, the mapping type property modifier (readonly or? A – or + prefix indicates that the modifier should be removed or added, which is what the Partial implementation in the previous chapter could look like

type Partial<T> = {
  [P inkeyof T]+? : T[P] }Copy the code

That is to say -? This removes the optional attribute modifier, making each attribute mandatory

StrictNullChecks also removes undefined if the attribute is a union type that contains undefined

interface TestNullCheck {
  // If there is no number type and only undefined type, undefined is retainedtestParam? :number | undefined
}

type Test = Required<TestNullCheck> {testParam: number}
Copy the code
  • Application Scenario Example

Scenario opposite to Partial

Readonly

Make all properties read-only

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

This is basically the same implementation as Partial and Required, except that the property modifier is readonly without the modifier prefix

The readonly modifier makes the modified property read-only (re-written cannot be overridden), but does not apply to the property’s children

  • Application Scenario Example

    1. Refer to the declaration of Object.freeze

    2. You can use Readonly for constants defined in certain projects to prevent them from being accidentally modified in other locations during subsequent maintenance

Pick

Construct a new type by selecting a set of attributes from type T

  • The source code
/** * 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
  • The source code parsing

When using a Pick, need to pass two generic parameters, the first parameter for an object type (or mapping type), the second parameter for the first parameter key (properties) of joint type (or a single literal type), Pick a new type of structure, the properties for the second parameter of the joint type of all members of the joint type

Example:

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}
// Union type
type NameAndAge = Pick<Dogs, "dogName" | "dogAge"> // { dogName: string; dogAge: number }

// A single string type
type DogKind = Pick<Dogs, "dogKind"> // { dogKind: string; }
Copy the code

In the implementation of Pick, a new syntax, generics, extends, was introduced

Extends in TS has a different meaning depending on where it is used. In this case, it means Generic Constraints. The left-hand side of extends must be assignable to the right-hand side

Keyof T is an object type, so keyof T is a combination of string or number (no symbol). Thus K is a subtype of the union type formed by all the attribute names of T

You can refer to the Partial section for the in mapping type. In Pick, K is iterated, P is a literal type and an attribute name of T in each iteration. Access T[P] through the index to get the specific type of the attribute value corresponding to the attribute name, and finally Pick gets a new object type

  • Application Scenario Example

    1. The situation where all attributes are needed in one position and only some attributes are needed in other positions, such as the Dogs example above

    2. Refer to the declaration and implementation of Lodash.pick

    3. Re-encapsulate third-party components and expose only some parameters

Record

  • The source code

Construct a new type based on a union type with an attribute key of K and an attribute value of T

/** * 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
  • The source code parsing

The meaning of Record source is easier to understand, that is, each attribute in K, is converted to T type

To use it

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}

type KeyofDogs = keyof Dogs // "dogName" | "dogAge" | "dogKind"

type StringDogs = Record<KeyofDogs, string>
// StringDogs is the same type as the following
type StringDogs = {
  dogName: string
  dogAge: string
  dogKind: string
}
Copy the code

But you might not understand keyof any

type KeyofAny = keyof any
/ / is equivalent to
type KeyofAny = string | number | symbol
Copy the code

The types constrained by KeyofAny in the code above can be the following types

type A = "a"
type B = "a" | "b"
type C = "a" | "b" | string
type D = "a" | 1
type E = "a" | symbol
type F = 1
type G = string
type H = number
type I = symbol
type J = symbol | 1
Copy the code

That is, a combination of symbol, number, or string combinations, or a literal type, or a literal type

As for keyof unknown and keyof never, they both return never

  • Application Scenario Example

    Record

    {[key: string]: string}
    ,>

    2. Use it in policy mode

type DogsRecord = Record<
  "dogKind1" | "dogKind2".(currentAge: number) = > number
>
function getRestAgeByCurrentAgeAndKinds(
  kind: "dogKind1" | "dogKind2",
  currentAge: number
) {
  // Calculate the possible remaining ages for different types of dogs
  const dogsRestAge: DogsRecord = {
    dogKind1: function (currentAge: number) {
      return 20 - currentAge
    },
    dogKind2: function (currentAge: number) {
      return 15 - currentAge
    },
  }
  return dogsRestAge[kind](currentAge)
}
getRestAgeByCurrentAgeAndKinds("dogKind1".1)
Copy the code

Exclude

Construct the type by excluding from the union type members of T all union members that can be assigned to type U

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

Example of using Exclude

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}

type KeyofDogs = keyof Dogs // "dogName" | "dogAge" | "dogKind"

type KeysWithoutKind = Exclude<KeyofDogs, "dogKind"> // "dogName" | "dogAge"
Copy the code

In the source code of Exclude, a new syntax, Conditional Types, is introduced

Extends of a condition type has a different meaning from extends in generics, which represents a constraint. The former extends is a judgment (assignable) that determines whether the left side of the extends type can be assigned to the right. The type to the left of the colon if possible, otherwise the type to the right (as opposed to the js true? 1:2

In the above example, you might think, KeyofDogs cannot be assigned to “dogKind” type, will get the T type, namely KeyofDogs type itself, but the actual result is “dogName” | “dogAge.” Remove the “dogKind” type from KeyofDogs

From the existing conditions, we cannot see the principle of Exclude. TS has a special case for Conditional Types, that is, Distributive Conditional Types, which is defined when the Conditional type applies to the generic type and the generic type is a union type. Then it is a distributed condition type

Generic types are well understood, that is, T in Type Example

= T is a generic type

T extends U? never : T, which is a generic type, is also a conditional type. A type that satisfies the distribution condition is defined by comparing each member of the union type T to the type to the right of extends. KeyofDogs in the above code is a union type. Into a generic type T, “dogName” | “dogAge” | “dogKind” will, in turn, with “dogKind” compare, only “dogKind” can be assigned to “dogKind”, but the type is never, The other two cannot be assigned to a “dogKind”, get their own literal type “dogName” and “dogAge”, they are composed of joint type “dogName” | “dogAge” is the final result

Other scenarios:

What if the first argument to Exclude is not of a union type?

type ExampleA = Exclude<1.2> // It takes the normal condition type, 1 cannot be assigned to 2, and gets the type of the first generic parameter, which is the literal type 1

type ExampleB = Exclude<{ 2: string }, 2> {2: string} {2: string}
Copy the code
  • Application Scenario Example

    1. Use in conjunction with the mapping type and refer to the implementation of Omit

Extract

The type is constructed by extracting from T’s union type members all union members that can be assigned to type U

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

In the Exclude section, we talked about distribution condition types. Extract is just the opposite of Exclude. In Exclude, the union type members in T are compared with type U in turn, and if they can be assigned to type U, the type is obtained

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}

type KeyofDogs = keyof Dogs // "dogName" | "dogAge" | "dogKind"

type KeysOnlyKind = Extract<KeyofDogs, "dogKind"> // "dogKind"
Copy the code
  • Application Scenario Example

    1. Use in conjunction with the mapping type and refer to the implementation of Omit

// Construct a new type by extracting some (or all) of the keys of type T
type Include<T extends object, U extends keyof any> = {
  [Key in Extract<keyof T, U>]: T[Key]
}
/ / or
type Include<T, K extends keyof any> = Pick<T, Extract<keyof T, K>>
Copy the code

Omit

Construct a new type by removing the keys of type T that intersect all union type members of K

  • The source code
/** * 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

Omit source code utilizes Pick and Exclude, which constructs a type of a union type member based on the first parameter and whose attribute is the second parameter (the union type)

The first argument is T, and the second argument is Exclude

. Exclude the first argument is keyof T, which is the union type of all the keys of T

K is the general type of external affix, and will also be passed to Exclude as the second parameter, from which a keyof T is obtained to eliminate the joint type formed by the intersection with K

The key of the new type generated by Pick will then contain only the union type members of the union type derived from Exclude

Ultimately, a new type will be constructed by deleting keys from type T that overlap with all members of the union type K

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}

type DogsWithoutKind = Omit<Dogs, "dogKind"> // { dogName: string; dogAge: number; }
Copy the code
  • Application Scenario Example

    1. Replace the default attribute type with it when component wrapping HTML elements

import _ from "lodash"
import React from "react"

type InputSize = "large" | "middle" | "small"
type InputName = "first-name-input" | "last-name-input" | "address-input"
type CoverAttr = "size" | "name"
interface InputProps
  extendsOmit<React.InputHTMLAttributes<HTMLInputElement>, CoverAttr> { size? : InputSize name? : InputName }const Input: React.FC<InputProps> = (props) = > {
  const classNames = `${props.className} ${props.size}`
  const omitProps = _.omit(props, ["size"."name"])

  return <input {. omitProps} className={classNames} />
}

Input.defaultProps = {
  size: "middle",}Copy the code
  1. During secondary encapsulation of third-party UI components, replace their parameters

  2. Others (components, functions, objects, etc.) are provided to the consumer by omitting some of the processed arguments

interface Dogs {
  dogName: string
  dogAge: number
  dogKind: string
}
/* * Dog cleaning register, register the dog's name (assuming the dog's name is unique) and return a certificate * by virtue of the certificate and the dog's type and age (assuming the age remains the same) to the cleaning place */
const wash = (dog: Dogs) = > {
  / * * * / wash dog
}
// Registered dog
const queue = new Set<string> ([])function dogsCleanRegister(dog: Dogs) {
  queue.add(dog.dogName)

  return function washTicket(dogNeedCheckInfo: Omit<Dogs, "dogName">) {
    if (
      dogNeedCheckInfo.dogAge === dog.dogAge &&
      dogNeedCheckInfo.dogKind === dog.dogKind
    ) {
      wash(dog)
      queue.delete(dog.dogName)
    } else {
      throw new Error("The voucher doesn't match the dog.")}}}// I registered with my own dog
const myDog = {
  dogName: "Xiao Ming".dogAge: 5.dogKind: "Corgi",}const goToWash = dogsCleanRegister(myDog)

// I wash someone else's dog
const myBrothersDog = {
  dogName: "Ming".dogAge: 6.dogKind: "Husky",}// Verification failed
goToWash(myBrothersDog) // 'Credentials do not correspond to dogs'
Copy the code

NonNullable

The new type cannot be null

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

NonNullable type under the condition of also used in the distribution, if the generic type T is joint type, its each joint type members can be assigned to null | undefined type is (never, null, and undefined) will be removed

As follows:

// In the case of dog names, stray dogs may be null if no one has named them, and newborn dogs may be undefined if they have not been named yet
type DogsName = 'husky' | 'corgi' | null | undefined

// When washing dogs in shops, no dogs without names are allowed
type NonNullableDogsName = NonNullable<DogsName> / / get the type NonNullableDogsName = "husky" | "corgi"
Copy the code

In addition, when an argument is passed that is not a union type, the type itself is returned, except for null and undefined

// any
type NonNullableAny = NonNullable<any> // The any condition type is special, resulting in a combination of the two branch types
// unknown
type NonNullableUnknown = NonNullable<unknown> // unknown
// never
type NonNullableNever = NonNullable<never> // never
// null
type NonNullableNull = NonNullable<null> // never
Copy the code
  • Application Scenario Example

    1. Filter out null and undefined types
/ / strictNullChecks mode
// The function type is defined in the Parameters section
// The method of washing the animal, which records the information to be provided, although there is a method, but there may be no staff, this is undefined
interfaceWashFunctions { washDogs? :(params: { dogName: string; dogAge: number }) = > voidwashCats? :(params: { catName: string; catAge: number }) = > void
}
// Suppose you want to automatically generate form information from the animal washing method for the customer to fill in
// Demand, extract the parameter type
// Parameters is used to extract the type of the parameter list for the function type, as described in the next section
// Parameters can only be passed function types
type ParamsByCallbackMap = {
  [Key inkeyof WashFunctions]-? : Parameters<NonNullable<WashFunctions[Key]>>[0]}/ / get
/** *type ParamsByCallbackMap = { * washDogs: { * dogName: string; * dogAge: number; *}; * washCats: { * catName: string; * catAge: number; *}; *} * /
Copy the code

Parameters

Construct a tuple type based on the parameter types of function type T

  • The source code
/** * Obtain the parameters of a function type in a tuple */
type Parameters<T extends(... args:any) = >any> = T extends(... args: infer P) =>any ? P : never;
Copy the code
  • The source code parsing

Before understanding the principle of Parameters, we must first know, how to define the type of function

  1. The most common and simple way to do this is to use type aliases (function-type expressions)
type Func1 = (. args:string[]) = > string
type Func2 = (arg1: string, arg2: number) = > string
type Func3 = (arg1: string, arg2: number. args:Array<number>) = > string
const arrowFunc: Func3 = (
  arg1: string, 
  arg2: number. args:Array<number>
) = > arg1 + [arg2, ...args].reduce((preTotal, current) = > preTotal + current, 0)
Copy the code
  1. Defined using an interface (in the code belowFunc3Syntax for theCall-signatures are called)
// Func['func1'] and Func['func2'] are function types
interface Func {
  func1(arg: number) :number
  func2(arg: string) :number
}
const func: Func = {
  func1(arg: number) {
    return arg
  },
  func2: (arg: string) = > Number(arg)
}
// Func3 is the function type
interface Func3 {
  (arg: number) :string
}
const func3: Func3 = (arg: number) = > {
  return arg.toString()
}
Copy the code
  1. Reloading with interfaces (actually merging interfaces)
interface Func {
  (arg: number) :string
  (arg: string) :string
}
const func: Func = (arg: number | string) = > {
  return arg.toString()
}
Copy the code
  1. Type definition with Declare (contextual typing)
declare function Func(. args:string[]) :string
const func: typeof Func = (. args:string[]) = >{
  return args.join(' ')}Copy the code
  1. Function overloads (function-overloads)
function func4(. args:string[]) :string
function func4(. args:number[]) :string
function func4(. args: (string | number) []) {
  return args.join(' ')}Copy the code

From the documentation: When inferring from a type that has more than one calling signature (for example, the type of an overloaded function), inferring from the last signature. Overloaded resolution cannot be performed based on the argument type list

  1. Other ways (see below, or refer to official documentation)

Constraints on Parameters generic T are (… Args: any) => any (Function type has no content or signature, can not be assigned to it). Any of the above five ways to define Function types can be assigned to this type

The implementation of Parameters also uses conditional types. If the generic T can be assigned to (… Args: Infer P) => any, and the infer type is P

The infer keyword is found in conditional types. Infer lets TS infer on its own and stores the inferred results into a type variable. Infer can only be used with extends statements

A couple of examples of derivations using it

type Example1 = Array<number> extends Array<infer T> ? T : never // number
type Example2 = { a: string } extends { a: infer T } ? T : never // string
Copy the code

Seeing the above two examples, you may already understand the Parameters source code

But here’s the thing. A TS subtype is based on a structure subtype. As long as the structure is compatible, it’s a subtype

If the left-hand type is an object type, in the example below, only Example3, the left-hand side cannot be assigned to the right-hand side because it lacks the B attribute

The subtypes of an object type must contain all the attributes and methods of the source type (more but not less)

type Example1 = { a: string } extends { a: string}?true : false // true
type Example2 = { a: string; b: number } extends { a: string}?true : false // true
type Example3 = { a: string; } extends { a: string; b: number}?true : false // false
Copy the code

If the left-hand type is a union type, in the following example, only Example2, the left-hand side cannot be assigned to the right-hand side because ‘b’ cannot be assigned to ‘a’

In a union type, the subtypes must have only some or all of the members of the source type (fewer but not more)

type Example1 = 'a' extends 'a' ? true : false // true
type Example2 = 'a' | 'b' extends 'a' ? true : false // false
type Example3 = 'a' extends 'a' | 'b' ? true : false // true
Copy the code

So what does a subtype of a function type look like?

To understand this, you need to know that concepts like covariant and contravariant also exist in TS

For function types, the type compatibility of function parameters is reverse, called inverse, and the type compatibility of return values is forward, called covariant

Check out some advanced TypeScript tricks you may not know about

Function parameters: I can use only some (or all) of the properties or methods you send me

As for the example of dog washing, customers who bring their dogs to the shop for cleaning need to explain the information of dogs to our shop staff, but no matter how much information you say, the shop staff only use the age and breed

// Shop assistants get information about dogs
function staffGetDogInfo(params: { dogAge: number; dosKind: string }) :void {
    document.write(params.dogAge + params.dosKind) // Write the registration information on the paper
}
The receiveAction method is used to receive the client's dog information
function customerSayDogInfo(receiveAction: (params: { dogAge: number; dosKind: string; dogName: string}) = >any) {
    const dogInfo = {
        dogAge: 1.dosKind: "husky".dogName: "The dog egg",
    }
    receiveAction(dogInfo)
}
// If the TS type verification is successful, staffGetDogInfo can be assigned to the customerSayDogInfo parameter type receiveAction
customerSayDogInfo(staffGetDogInfo)
Copy the code

The example shows a single argument, and the same rule applies to the number of arguments. You can give me more arguments, but I don’t have to

Is to sum up, the two function types, less function parameters (or parameter equal number) and corresponding property/method of location parameter need fewer (no more than another type corresponding property/method of location parameter need number, but pay attention to the joint types and object types are reversed, members of the joint type position than the corresponding parameter type members of the union type), Is a function subtype without considering the return value of the function

You must give me what I want, more but no less

Continuing with the example above, we modify the scenario so that the type of parameter still satisfies the rule of function subtype. In addition to the information required by the clerk, the customer will also say something that is not needed, and the clerk will give a return receipt after recording it

What the customer wants to know from the receipt is, when does it start, when does it finish, and who will wash my dog

// The shop assistant gets the dog's information and returns the receipt after registration
function staffGetDogInfo(params: { dogAge: number; dosKind: string }) :{
  washPersonName: string.washStartTime: string.washEndTime: string.payedMoney: number.isVip: boolean
} {
    document.write(params.dogAge + params.dosKind) // Write the registration information on the paper
    // Return receipt
    return {
      washPersonName: 'staff Alice'.washStartTime: '2021/5/25 20:00:00'.washEndTime: '2021/5/25 20:30:00'.payedMoney: 100.isVip: true}}The receiveAction method is used to receive the client's dog information
// When you get the receipt, check the end time on the receipt and come back later
function customerSayDogInfo(
  receiveAction: (
    params: { dogAge: number; dosKind: string; dogName: string }
  ) => { washEndTime: string }
) {
    const dogInfo = {
        dogAge: 1.dosKind: "husky".dogName: "The dog egg",}const receipt = receiveAction(dogInfo)
    setTimeout(() = > {
      // Come back for the dog
    }, Number(new Date(receipt.washEndTime)) - Number(new Date()))}// If the TS type verification is successful, staffGetDogInfo can be assigned to the customerSayDogInfo parameter type receiveAction
customerSayDogInfo(staffGetDogInfo)
Copy the code

Combining the above two examples, it is easy to get: Less two function types, function parameters (or parameter equal number) and corresponding property/method of location parameter need fewer (no more than another type corresponding property/method of location parameter need number, but pay attention to the joint types and object types are reversed, members of the joint type position than the corresponding parameter type members of the union type), When the return value type of a function contains more attributes or methods than another function type (note that the union type has fewer members), the type is a function subtype

When the left and right sides of extends are function types, it’s easy to see which branch type you get

  • Usage scenarios

    1. Higher-order functions. In some scenarios, Parameters can be used to extract the parameter types of the function passed in without using generics

ConstructorParameters

Construct a tuple or array type from the parameter types of the constructor type T (or never if T is not a function)

  • The source code
/** * Obtain the parameters of a constructor function type in a tuple */
type ConstructorParameters<T extends new(... args:any) = >any> = T extends new(... args: infer P) =>any ? P : never;
Copy the code
  • The source code parsing

The source code for ConstructorParameters is very similar to the source code for Parameters, except that a new is added before the function type

The syntax of writing a new keyword in front of a function type is called Construct Signatures in TS

Construction signatures are typically used in declarations of constructors native to the JS runtime environment (existing apis) or in.d.ts declaration files

If you write a constructor, do not use the constructor signature for type definition, because it is difficult to define

In other cases, you can use it as a constraint type (for example, the type defining a function parameter must be a constructor) rather than as a direct type declaration for a function or class

ConstructorParameters are also used similarly to Parameters

class Dog {
  private dogAge: number
  private isMale: boolean
  private dogKind: string
  constructor(isMale: boolean, dogKind: string) {
    this.dogAge = 0
    this.isMale = isMale
    this.dogKind = dogKind
  }
}
type DogGaveBirthNeedInfo = ConstructorParameters<typeof Dog> // Get type [Boolean, string]
Copy the code

ReturnType

Construct a new type based on the return value type of function type T

  • The source code
/** * Obtain the return type of a function type */
type ReturnType<T extends(... args:any) = >any> = T extends(... args:any) => infer R ? R : any;
Copy the code
  • The source code parsing

Unlike the Parameters source code, its INFER R is at the return value of the function type

function washDog() {
  return {
    dogName: 'linlin'.dogAge: 20.dogKind: 'husky'}}type WashTicket = ReturnType<typeof washDog> 
Type WashTicket = {* dogName: string * dogAge: number * dogKind: string *} */ 
Copy the code
  • Application Scenario Example

    1. Higher-order functions. Without generics, some scenarios can use ReturnType to extract the return value type of the passed function

InstanceType

Construct a new type based on the return value of a constructor type T

  • The source code
/** * Obtain the return type of a constructor function type */
type InstanceType<T extends new(... args:any) = >any> = T extends new(... args:any) => infer R ? R : any;
Copy the code
  • The source code parsing

InstanceType differs from ReturnType in that it has a construction signature, and from ConstructorParameters in that it extrapolates not the parameter type but the return value type

class Dog {
  private dogAge: number
  private isMale: boolean
  private dogKind: string
  constructor(isMale: boolean, dogKind: string) {
    this.dogAge = 0
    this.isMale = isMale
    this.dogKind = dogKind
  }
}
type DogGaveBirthNeedInfo = InstanceType<typeof Dog> // Get the Dog type
Copy the code

You might wonder, why did you get Dog?

Please see below

A class defined by a class is itself a type, and the type of its instance can be described by itself

For example, Dog[‘dogAge’] can get the type number of the private dogAge property of the instance

Uppercase

Uppercase the literal type of a string

Uppercase is built-in to the compiler and can be used with template String Types. For details, see The Intrinsic String Manipulation Types (see Commit)

  • The source code
/** * Convert string literal type to uppercase */
type Uppercase<S extends string> = intrinsic;
Copy the code
  • usage
type DogName = "LinLin"
type UppercaseDogName = Uppercase<DogName> / / get LINLIN ""
Copy the code

If you pass in a union type, you get a new type with each member uppercase (twenty-six letters)

If you pass in type any or string, you get themselves

Lowercase

Converts the literal type of a string to lowercase

The Lowercase implementation is built into the compiler

  • The source code
/** * Convert string literal type to lowercase */
type Lowercase<S extends string> = intrinsic;
Copy the code
  • usage
type DogName = "LinLin"
type LowercaseDogName = Lowercase<DogName> / / get linlin ""
Copy the code

Capitalize

Converts the literal type of a string to uppercase

The Capitalize implementation is built into the compiler

  • The source code
/** * Convert first character of string literal type to uppercase */
type Capitalize<S extends string> = intrinsic;
Copy the code
  • usage
type DogName = "linlin"
type CapitalizeDogName = Capitalize<DogName> / / get LinLin ""
Copy the code

Uncapitalize

Converts the literal type initial of a string to lowercase

The implementation of Uncapitalize is built into the compiler

  • The source code
/** * Convert first character of string literal type to lowercase */
type Uncapitalize<S extends string> = intrinsic;
Copy the code
  • usage
type DogName = "LinLin"
type UncapitalizeDogName = Uncapitalize<DogName> / / get linlin ""
Copy the code
  • Usage scenarios

    1. The above four string manipulation types can be used in conjunction with the template string type to achieve advanced type definition

ThisType

Enhances the type of this in the object literal type

  • The source code
/** * Marker for contextual 'this' type */
interface ThisType<T> { }
Copy the code

Except in object literal types (with –noImplicitThis enabled), the rest is simply an empty interface, as shown in the example in the documentation ThisType

ThisParameterType

Extract the this type declared by the function

  • The source code
/** * Extracts the type of the 'this' parameter of a function type, or 'unknown' if the function type has no 'this' parameter. */
type ThisParameterType<T> = T extends (this: infer U, ... args:any[]) = >any ? U : unknown;
Copy the code
  • The source code parsing

If you need to use this in the implementation of a function, you can declare it in the first argument position (the function argument type is successively followed by the first argument), as shown below

interface Dog {
  voice: {
    bark(): void}}function dogBark(this: Dog) {
  this.voice.bark()
}
// If this is not declared explicitly, this is derived from the context in which the function is declared
After declaring this, on a function call, TS verifies that this in the current context matches the desired this
// dogBark is called as follows
declare const dog: Dog
dogBark.call(dog)
/ / or
declare const dog: Dog & { dogBark: typeof dogBark }
dog.dogBark()
Copy the code

Do not define this in arrow functions; the this of arrow functions cannot be changed

ThisParameterType and Parameters are implemented similarly, and are infer based. ThisParameterType is the type of this, and unknown if this is not explicitly declared

OmitThisParameter

Construct a function type based on a function type without this declaration

  • The source code
/** * Removes the 'this' parameter from a function type. */
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends(... args: infer A) => infer R ?(. args: A) = > R : T;
Copy the code
  • The source code parsing

This is kind of like putting an optional property’s property modifier, right? TS provides a way to get rid of this modifier, and OmitThisParameter is another way to get rid of this that TS already has

OmitThisParameter can accept a function type T, if ThisParameterType

returns unknown (this is not shown or is not a function type), then return T directly, otherwise it will match the type T with the type (… Args: infer A) => infer R compares and extracts parameter type A and return value type R. If the former (T) is A subtype of the latter, A new function type will be obtained with parameter type A and return value type R; otherwise, T itself will be obtained

Note that a function type with this is a subtype of a function type that does not have this but has the same argument type

Non-built-in self-implementing Utility Types

Which of the following tool types have you used? What other tool types have you written yourself? Share them in the comments section

DeepPartial

type DeepPartial<T> = {
    [Key inkeyof T]? : T[Key]extends object
      ? DeepPartial<T[Key]>
      : T[Key]
}
Copy the code

ReadonlyPartial

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

ReadWrite

Also called Mutable, it removes the read-only modifier and is readable and writable

type ReadWrite<T> = {
  -readonly [P in keyof T]: T[P]
}
Copy the code

GetPromiseType

Extract the generic parameters of the Promise

type GetPromiseType<P extends Promise<any>> = P extends Promise<infer Params>
  ? Params
  : never
Copy the code

You can use this in conjunction with ReturnType to extract the return value of an asynchronous function

ChangeRecordType

Set all properties of the object to T, with keyof Object as the first argument, and undefined if no second argument is passed

type ChangeRecordType<K, T = undefined> = {
  [P inkeyof K]? : T }Copy the code

Values

Construct a union type for each value of the passed type, see Object.values

type Values<T> = T[keyof T]
Copy the code

Include

Extract some (or all) keys of type T to construct a new type, as opposed to Omit

/ / write 1
type Include<T extends object, U extends keyof any> = {
  [Key in Extract<keyof T, U>]: T[Key]
}
// Remap 4.1 new syntax
type Include<T extends object, U extends keyof any> = {
  [Key in keyof T as Key extends U ? Key : never]: T[Key]
}
/ / writing 3
type Include<T, K extends keyof any> = Pick<T, Extract<keyof T, K>>
Copy the code

Nullable

Generates a union type that can be null

type Nullable<T extends keyof any> = T | null | undefined
Copy the code

NullableValues

Each attribute value is allowed to be null

Note: Nullable is used above

type NullableValue<T> = {
  [Key inkeyof T]? : Nullable<T[Key]> }Copy the code

Proxify

type Proxify<T> = {
 [P in keyof T]: { 
   get(): T[P]
   set(v: T[P]): void}}Copy the code

SumAggregate

And set

type SumAggregate<T, U> = T | U
Copy the code

Diff

differences

type Diff<T, C> = Exclude<T, C> | Exclude<C, T>
Copy the code

Flatten (TupleToUnion)

Tuple to union type

type Flatten<T> = T extends Array<infer U> ? U : never
/ / write 2
type Flatten<T extends any[]> = T[number]
Copy the code

GetterSetterPrefix

Prefixes set and GET to existing attributes

type GetterSetterPrefix<T> = {
    [Key in keyof T as Key extends string ? `get${Uppercase<Key>}` : never]: {
        (): T[Key];
    }
} & {
    [Key in keyof T as Key extends string ? `set${Uppercase<Key>}` : never]: {
        (val: T[Key]): void;
    }
} & T
Copy the code

ExcludeValues

Discard the attribute names of type T whose values can be assigned to V and construct a new type

type ExcludeValues<T, V> = {
    [Key in keyof T as T[Key] extends V ? never : Key]: T[Key]
}
Copy the code

ExtractValues

Preserve the names of attributes in type T whose values can be assigned to V and construct a new type

type ExtractValues<T, V> = {
    [Key in keyof T as T[Key] extends V ? Key : never]: T[Key]
}
Copy the code

ChainedAccessUnion

Construct a string union type that describes a chain of attributes accessible to the object type

Note: Values above are used

type ChainedAccessUnion<
    T,
    A = {
        [Key in keyof T]: T[Key] extends string ? never : T[Key]
    },
    B = {
        [Key in keyof A]: A[Key] extends never
            ? never
            : A[Key] extends object
            ? `${Extract<Key, string>}.${Extract<keyof A[Key], string>}` | (ChainedAccessUnion<A[Key]> extends infer U ? `${Extract<Key, string>}.${Extract<U, string>}` : never)
            : never
    }
> = T extends object ? Exclude<keyof A | Exclude<Values<B>, never>, never> : never
Copy the code

As shown, Diff gets never, and there is no difference between the two (both union types)

conclusion

Technology can’t solve every problem, but our familiarity with technology sets the limit to how much we can use it to better solve problems

So for TS, do you want to learn it again?

Original text: yzl. Xyz/Lin / 2021/05…