This article, the second in a series on Playing with TypeScript tool Types, covers the following parts:

  • Mandatory: extends condition operator
  • Exclude<Type, ExcludeUnion>
  • Extract<Type, Union>
  • NonNullable<Type>
  • Mandatory: tuple type Tuple type
  • Parameters<Type>
  • ConstructorParameters<Type>
  • ReturnType<Type>
  • InstanceType<Type>

Quick jump

  • Playing with TypeScript tool types (Part 1)
  • Playing with TypeScript tool Types (2)

Mandatory: extends condition operator

Since the extends keyword is covered in the subsequent source code, it is important to know this in advance to make it easier to understand the source code. See TypeScript conditional types for more information on TypeScript conditional types.

7. Exclude<Type, ExcludeUnion>: excluding

Excluding all members of the ExcludeUnion union Type from Type can be understood as taking the difference set. The rest is returned as a new type.

>> Source code interpretation


type Exclude<T, U> = T extends U ? never : T;

Copy the code

How do I understand T extends U? Never: What about T? Determines whether each item in T can be assigned to type U, and returns never if so, or the current item if not.

Let’s use a specific example to understand this sentence.

>> Actual usage

/ / out of a specific value type T0 Exclude < = "a" | | "b" "c", "A" "b" / > | a type "c" / / Exclude type T1 = Exclude < string | number | () = > (void), Function >Copy the code

In the case of T0, how does the extends keyword work?

Type T0 = Exclude < "a" | | "b" "c", "a" > / / "b" | "c" / / equivalent to type T0 = "a" extends "a"? never : "a" | "b" extends "a" ? never : "b" | "c" extends "a" ? Never: "c" / / equivalent to type T0 = never | | "b" "c" / / equivalent to type T0 = "b" | "c"Copy the code

If you don’t understand the equivalence here, read the TypeScript conditional types recommended at the beginning

8. Extract<Type, Union>Extract:

Take the common part of both Type Type and Union Type and return it as a new Type.

>> Source code parsing


/**

 * Extract from T those types that are assignable to U

 */

type Extract<T, U> = T extends U ? T : never;

Copy the code

Extract the types of type T that can be assigned to type U. So the difference between this and Exclude in the source code is that never is placed on the branch where the conditional operation is false.

So if you understand Exclude, it’s not complicated to understand Extract.

>> Actual usage

/ / extracts the value of a specific type T0 Extract < = "a" | | "b" "c", "A" "b" / > | a type "c" / / extraction type T1 = Extract < string | number | () = > (void), Function >Copy the code

I won’t repeat extends’s processing logic here.

9. NonNullable<Type>

Filter out null and undefined in Type, and return the remaining Type as a new Type. It’s actually a special case of Exclude.

>> Source code parsing


type NonNullable<T> = T extends null | undefined ? never : T

Copy the code

Can be found and Exclude < U > T, source code is very like, just change U to null | is undefined. So the combination of Exclude

makes sense.
,>

>> Actual usage


type T0 = NonNullable<string | number | undefined>;

//  type T0 = string | number

type T1 = NonNullable<string[] | null | undefined>;

//  type T1 = string[]

Copy the code

Mandatory: tuple type Tuple type

A tuple type is an array type with a fixed number of elements and a definite element type.

Tuple types allow you to express an array with a fixed number of elements whose types are known, but need not be the same.

For example, here is a tuple type:


let x: [string.number];

Copy the code

When we access an element of declared type, we can get the correct type checking:


// OK

console.log(x[0].substring(1));

// Property 'substring' does not exist on type 'number'.

console.log(x[1].substring(1));

Copy the code

When we access subscripts that exceed the length of the array, we get the type undefined, and we get an error message that there are no accessible elements in the index:


let x: [string, number];

x = ["hello", 10]; // OK

// Type '"world"' is not assignable to type 'undefined'.

// Tuple type '[string, number]' of length '2' has no element at index '3'.

x[3] = "world";

// Object is possibly 'undefined'.

// Tuple type '[string, number]' of length '2' has no element at index '5'.

console.log(x[5].toString());

Copy the code

10. Parameters<Type>

Construct a tuple type based on the type of a function argument. So the utility type is used to get the type of the function argument.

>> Source code parsing

/** * 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

There are two key parts in the source code:

  • T extends (… Args: any) => any, which specifies that T must be a function (except any and never) and that the argument is of type any, so the argument can be of any type.

  • T extends (… args: infer P) => any ? P: never. If T is a function type, then the extends extends branch is true and returns a P type, which is the type of the args parameter

And if you can’t infer, a little bit more

>> Actual usage

declare function f1(arg: { a: number; b: string }): void; type T0 = Parameters<() => string>; // type T0 = [] type T1 = Parameters<(s: string) => void>; // type T1 = [s: string] type T2 = Parameters<<T>(arg: T) => T>; // type T2 = [arg: unknown] type T3 = Parameters<typeof f1>; // type T3 = [arg: { a: number; b: string; }] type T4 = Parameters<any>; // type T4 = unknown[] type T5 = Parameters<never>; // type T5 = never type T6 = Parameters<string>; // Type 'string' does not satisfy the constraint '(... args: any) => any'. type T7 = Parameters<Function>; // Type 'Function' does not satisfy the constraint '(... args: any) => any'. // Type 'Function' provides no match for the signature '(... args: any): any'.Copy the code

Parameters are passed never and any.

11. ConstructorParameters<Type>

Returns the parameter type of the constructor as a tuple type. We already know how to get a function’s parameter type, [Parameters](# 9.). If we want to obtain the type of the constructor parameter, we should first determine which one is the constructor, and then obtain the type of the parameter.

>> Source code parsing

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

There are two points of concern here:

  • The abstract keyword modifies functions called abstract methods, and abstract methods can only occur in abstract classes

  • new (… Args: any) => any, which is the definition of the constructor

So, the ConstructorParameters tool type works only with abstract classes, never and any we don’t care about.

>> Actual usage

Take a closer look at ErrorConstructor with a concrete example:

interface ErrorConstructor { new(message? : string): Error; (message? : string): Error; readonly prototype: Error; } type T0 = ConstructorParameters<ErrorConstructor>;Copy the code

How do ConstructorParameters work here? T extends abstract new (… args: infer P) => any ? P : never; If the condition is met, the parameter type P is returned.


type T0 = string

Copy the code

12. ReturnType<Type>

Gets the return value type of the function. With our implementation of the function parameter type in [Parameters](# 10.), we can easily implement this tool type ourselves.

>> Source code parsing

/** * 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 only difference from the Parameters

implementation is that the infer position goes from the parameter position to the return value position. And that makes sense.

Note that the return value type is not a tuple type, because inParameters<Type>,argsIs an array, so it returns a tuple type, whereas the return value of a function can be of any type.

>> Actual usage

declare function f1(): { a: number; b: string }; type T0 = ReturnType<() => string>; // type T0 = string type T1 = ReturnType<(s: string) => void>; // type T1 = void type T2 = ReturnType<<T>() => T>; // type T2 = unknown type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // type T3 = number[] type T4 = ReturnType<typeof f1>; // type T4 = {a: number; b: string; } type T5 = ReturnType<any>; // type T5 = any type T6 = ReturnType<never>; // type T6 = never type T7 = ReturnType<string>; // Type "string" does not satisfy the constraint "(... Args: any) => any ". ts(2344) type T8 = ReturnType<Function>; // Type "Function" does not satisfy the constraint "(... Args: any) => any ". // The content provided by type "Function" and the signature "(... Args: any): any "is not matched. ts(2344)Copy the code

13. InstanceType<Type>

Gets the return type of the constructor instance. That is, the type of value returned after the new operator is called to the constructor. We already know how to get the type of the constructor parameter, so we can easily deduce how to get the type of the instance.

>> Source code parsing

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

Infer is also moved from the parameter position to the return value position.

>> Actual usage

Here we continue with FunctionConstructor as an example:

interface FunctionConstructor { /** * Creates a new function. * @param args A list of arguments the function accepts. */  new(... args: string[]): Function; (... args: string[]): Function; readonly prototype: Function; } type T0 = InstanceType<FunctionConstructor>;Copy the code

Combined with the new (… args: string[]): Function; Unknown:


type T0 = Function;

Copy the code

Try more on typescript Playground for yourself.

Quick jump

  • Playing with TypeScript tool types (Part 1)