[Typescript] type operation

Posted by marcs910 on Wed, 09 Mar 2022 12:32:13 +0100

summary

Everything in Javascript is a variable, and so is Typescript. A type can also be used as a "variable" to perform some operations. For example, the advanced type introduced earlier is an extended type based on the basic type and composite type.

Some operators in Typescript can perform further operations on types, including:

  • typeof: type acquisition
  • keyof: index type acquisition
  • as: type assertion
  • []: member type
  • Extensions: condition type

Operator

typeof: type acquisition

Javascript itself has a typeof operator, which is used to obtain the variable type name, and the return value is a string. Typeof in Typescript can be used for variables or types. When using variables, the effect is the same as that of Javascript. When using types, you can obtain the types of variables.

Example:

let a = { name: 'a', value: 0 }

type A = typeof a

let b: A = { name: 'b', value: 1 }

In the above example, A is the type obtained through typeof, which obtains the corresponding type through variables.

keyof: index type acquisition

The keyof operator accepts an object type and returns the literal type composed of its member names.

Example:

interface A {
  name: string
  value: number
}

type AMember = keyof A // 'name' | 'value'

In the above example, the type AMember is the literal type: 'name' | 'value'.

If the index member is declared in the object type, the keyof operation is the type of index name. Example:

interface A {
  [index: number]: string
}

type AMember = keyof A // number

When getting object members through index in Javascript, if the index name is a number, it will also be converted into a string. Therefore, in Typescript, if the index name is of string type, the result of keyof operation is' string '|' number '.

Applying: member name constraints

A common application is to use keyof to constrain object member names in generics.

Example:

function print<T, U extends keyof T>(target: T, name: U) {
  let value = target[name]

  console.log(value)
}

In the above example, first obtain the possible value (literal type) of the member name of type T through keyof T, and then indicate that the type variable U must contain the types allowed by the members of type T through generic constraints (extensions). The effect is that the parameter name must be one of the member names of the parameter target.

as: type assertion

Type assertions are often misunderstood as type conversions, but assertions only tell the compiler that a variable is of a more specific or broader type and do not transform the variable into another type. This often happens when the compiler does not know the specific type of a variable, but only knows that it is a base class, while the user knows that the variable is a derived class, which can clearly tell the compiler its type.

The keyword as or angle brackets < > are used in Typescript to represent type assertions. Example:

let a = undefined as string | undefined
a = 'a'

let b = <{ value: number } | null>null
b = { value: 0 }

'a' is an undefined variable and cannot be assigned to a string. We assert it as string | undefined, indicating that this type can be either string or undefined.

A common application is in browsers when we use document When getelementbyid method obtains the element object, the return value type of the method is HTMLElement, but we clearly know what type of element we obtain, so we can use type assertion to let the compiler know the specific type. Example:

let canvas = document.getElementById('canvas') as HTMLCanvasElement

[]: member type

Type can use the operator []. The operand is the member name and returns the type of the member.

Example:

interface A {
  name: string
  value: number
}

type AName = A['name'] // string

You can use literal union types to get a union type that consists of member types. Example:

type B = A['name' | 'value'] // string | number
type C = A[keyof A] // string | number

If the type has an index, we can also get its index value type. Example:

interface A {
  [index: string]: boolean
}

type B = A[string] // boolean

Note that since the string type index name can be compatible with the number type, the type B of the above example can be changed to: type B = A[number], but for the number type index name, the string type cannot be used to obtain the index value type.

Extensions: condition type

Expression < type > extends < target >< True expression >: < false expression > you can determine the return type by judging whether the type meets the conditions.

Example:

interface A {
  name: string
}
interface B extends A {
  value: number
}

type C = B extends A ? string : number

Type C is determined by whether type B is compatible with type A. if compatible, it is string type, otherwise it is number type.

The meaning of compatibility here will be introduced later.

This feature is often used in generics. Example:

type C<T> = T extends A ? string : number

type D = C<B> // string

infer

In order to enhance the ability of inferring types, Typescript adds the operator infer. Its function is "inferring". There is a type here. It can only be used in truth expression of condition type.

Example:

type Flatten<T> = T extends Array<infer Item> ? Item : Type

The function of this generic type is to return the element type if the T type is an array or a derived class of an array, otherwise return the type itself to achieve the purpose of "type flattening". Here, the function of infer Item is equivalent to "creating a type variable". It is speculated that there is a type for the following truth value expression.

Type diffusion

If the union type is passed into the generic type of conditional type expression, the types in the union type will be split and then combined.

Example:

type ToArray<T> = T extends any ? T[] : never

type StrArrOrNumArr = ToArray<string | number> // string[] | number[]

It is equivalent to that string and number types are first split from string | number and enter toArray < T > to become string [] and number [], and then combined into string[] | number [].

If you want to avoid this behavior, you can write this:

type ToArrayNonDist<T> = [T] extends [any] ? T[] : never

type StrOrNumArr = ToArrayNonDist<string | number> // (string | number)[]

Topics: Javascript TypeScript