*ypeScript provides some of the more useful generic types, but we often overlook them. In this section we’ll look at these generics together.

Generic types

Partial<Type>

From a type, constructs a new type, where all properties of the new type are derived from the original type and all properties of the new type are optional.

1
2
3
4
5
6
interface Todo {
    title: string;
    description: string;
}

type PartialTodo = Partial<Todo>;

The PartialTodo type is equivalent to:

1
2
3
4
interface PartialTodo {
    title?: string | undefined;
    description?: string | undefined;
}

Required<Type>

Required<Type> has the opposite effect of Partial<Type>, constructing a new type where all attributes of the new type are derived from the original type, but all attributes of the new type are required (not optional).

1
2
3
4
5
interface Props {
    a?: number;
    b?: string;
}
type T = Required<Props>;

T is equivalent to:

1
2
3
4
interface T {
    a: number;
    b: string;
}

Readonly<Type>

Constructs a new type where all attributes of the new type are from the original type, and all attributes of the new type are read-only attributes .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
interface Todo {
    title: string;
}

const todo: Readonly<Todo> = {
    title: "Delete inactive users",
};

// 再次给title属性赋值,会报错
todo.title = "Hello";

Record<Keys, Type>

Constructs a new type whose attribute names are from Keys and whose attribute values are of type Type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
interface CatInfo {
    age: number;
    breed: string;
}

type CatName = "miffy" | "boris" | "mordred";

const cats: Record<CatName, CatInfo> = {
    miffy: { age: 10, breed: "Persian" },
    boris: { age: 5, breed: "Maine Coon" },
    mordred: { age: 16, breed: "British Shorthair" },
};

Pick<Type, Keys>

Constructs a new type that is a subset of type Type (the set of properties of the new type is a subset of the set of properties of type Type).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
    title: "Clean room",
    completed: false,
};

Omit<Type, Keys>

is similar to Pick<Type, Keys>, but as its name implies, Omit means to ignore. Omit<Type, Keys> constructs a new type with all attributes from Type, excluding the attributes specified by Keys from the attributes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Todo {
    title: string;
    description: string;
    completed: boolean;
    createdAt: number;
}

// TodoPreview包含title、completed、createdAt三个属性
type TodoPreview = Omit<Todo, "description">;

const todo: TodoPreview = {
    title: "Clean room",
    completed: false,
    createdAt: 1615544252770,
};

// TodoInfo包含title、description两个属性
type TodoInfo = Omit<Todo, "completed" | "createdAt">;

const todoInfo: TodoInfo = {
    title: "Pick up kids",
    description: "Kindergarten closes at 5pm",
};

Exclude<Type, ExcludedUnion>

Constructs a new union type (union), each item of which exists in the Type type but not in the ExcludedUnion type. Type and ExcludedUnion are union types (union).

1
2
3
4
5
6
7
8
// type T0 = "b" | "c"
type T0 = Exclude<"a" | "b" | "c", "a">;

// type T1 = "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;

// type T2 = string | number
type T2 = Exclude<string | number | (() => void), Function>;

Extract<Type, Union>

Constructs a new union type, each item of which exists both in Type and in Union. Type and Union are union types (union).

1
2
3
4
5
// type T0 = "a" 
type T0 = Extract<"a" | "b" | "c", "a" | "f">;

// type T1 = () => void
type T1 = Extract<string | number | (() => void), Function>;

NonNullable<Type>

Constructs a new type by excluding null and undefined from the Type.

1
2
3
4
5
// type T0 = string | number
type T0 = NonNullable<string | number | undefined>;

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

Parameters<Type>

Constructs a meta-ancestor type consisting of function parameters from a function type Type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
declare function f1(arg: { a: number; b: string }): void;
declare function f2( a: number, b: string): void;
// type T0 = []
type T0 = Parameters<() => string>;

// type T1 = [s: string]
type T1 = Parameters<(s: string) => void>;

// type T2 = [arg: unknown]
type T2 = Parameters<<T>(arg: T) => T>;

// type T3 = [arg: {
//     a: number;
//     b: string;
// }]     
type T3 = Parameters<typeof f1>;

// type T4 = unknown[]
type T4 = Parameters<any>;

// type T5 = never
type T5 = Parameters<never>;

// type T6 = [a: number, b: string]
type T6 = Parameters<typeof f2>;

ConstructorParameters<Type>

is similar to Parameters<Type>, but the Type of ConstructorParameters<Type> is the constructor type. It constructs a meta-ancestor type consisting of constructor parameters.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// type T0 = [message?: string]
type T0 = ConstructorParameters<ErrorConstructor>;

// type T1 = string[]
type T1 = ConstructorParameters<FunctionConstructor>;

// type T2 = [pattern: string | RegExp, flags?: string]
type T2 = ConstructorParameters<RegExpConstructor>;

// type T3 = unknown[]
type T3 = ConstructorParameters<any>;

ReturnType<Type>

Constructs a new type from the function return value type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
declare function f1(): { a: number; b: string };

// type T0 = string
type T0 = ReturnType<() => string>;

// type T1 = void
type T1 = ReturnType<(s: string) => void>;

// type T2 = unknown
type T2 = ReturnType<<T>() => T>;

// type T3 = number[]
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;

// type T4 = {
//     a: number;
//     b: string;
// } 
type T4 = ReturnType<typeof f1>;

// type T5 = any
type T5 = ReturnType<any>;

// type T6 = never
type T6 = ReturnType<never>; 

InstanceType<Type>

Constructs a new type from an instance type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class C {
  x = 0;
  y = 0;
}

// type T0 = C
type T0 = InstanceType<typeof C>;

// type T1 = any
type T1 = InstanceType<any>;

// type T2 = never
type T2 = InstanceType<never>;

ThisParameterType<Type>

Extracts the type from the this pointer in a function type. If the function does not have this, return unknow.

1
2
3
4
5
6
7
8
function toHex(this: Number) {
    return this.toString(16);
}

// n: Number
function numberToString(n: ThisParameterType<typeof toHex>) {
    return toHex.apply(n);
}

OmitThisParameter<Type>

Remove the this parameter from the function type and construct a new function type. If Type is not displayed with the declared this parameter, the result is equal to Type.

1
2
3
4
5
6
function toHex(this: Number) {
    return this.toString(16);
}

// const fiveToHex: () => string
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);

ThisType<Type>

-noImplicitThis must be turned on before using the ThisType<Type> generic type. The official documentation, which is a bit complicated for me, describes it this way.

This utility does not return a transformed type. Instead, it serves as a marker for a contextual this type. Note that the –noImplicitThis flag must be enabled to use this utility.

The official example given is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type ObjectDescriptor<D, M> = {
    data?: D;
    methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
    let data: object = desc.data || {};
    let methods: object = desc.methods || {};
    return { ...data, ...methods } as D & M;
}

let obj = makeObject({
    data: { x: 0, y: 0 },
    methods: {
        moveBy(dx: number, dy: number) {
            this.x += dx; // Strongly typed this
            this.y += dy; // Strongly typed this
        },
    },
});

obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

If you didn’t get the above example either, you can follow my way of understanding it. When you add & ThisType<WhateverYouWantThisToBe> to the type of an object, then the type of this parameter of all methods in that object will be WhateverYouWantThisToBe. So the this type of the moveBy method in the above example would be D & M.

Looking at an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface HelperThisValue {
    logError: (error: string) => void;
}

// helperFunctions中所有方法的this类型都为HelperThisValue
let helperFunctions: { [name: string]: Function } & ThisType<HelperThisValue> = {
    hello: function() {
        this.logError("Error: Something went wrong!"); // TypeScript successfully recognizes that "logError" is a part of "this".
        this.update(); // TS2339: Property 'update' does not exist on HelperThisValue.
    }
}

registerHelpers(helperFunctions);