On TypeScript type programming

I started this article Blog For reprint, please indicate the source: http://kohpoll.github.io/blog...

When we write TypeScript, we mainly use type annotation (adding type constraints to variables and functions), which can enhance code readability and avoid low-level bug s. In fact, TypeScript's type system is designed to be so powerful that it can be used as a programming language alone. This article is a summary of learning TypeScript type programming. I hope it will be helpful to you.

Before starting

This article will not explain the basic syntax and use of TypeScript. You can refer to the excellent materials provided on the Internet:

Launch/The Start

Refer to the description of programming language in SCIP. A programming language should provide the following mechanisms:

  • Basic expression. It is used to represent the simplest individual concerned by language.
  • Combination method. Construct composite objects from simple individuals.
  • Abstract methods. It can encapsulate composite objects as independent units.

Next, we will take these three aspects as clues to explore TypeScript's type programming.

Basic expression

Let's first look at the way to define "variables" in type programming:

// The values of string, number and boolean can be used as types, which are called literal type

type LiteralS = 'x';

type LiteralN = 9;

type LiteralB = true;

// Foundation type

type S = string;

// function

type F = (flag: boolean) => void;

// object

type O = { x: number; y: number; };

// tuple

type T = [string, number];

Here's the difference between interface and type.

The main difference is that type can be "type programmed", but interface can't.

The types that interface can define are relatively limited, that is, object/function/class/indexable:

// object

interface Point {

x: number;

y: number;


const p: Point = { x: 1, y: 2 };

// function

interface Add {

(a: number, b: number): number;


const add: Add = (x, y) => x + y;

// class

interface ClockConstructor {

new (hour: number, minute: number): ClockInterface;


interface ClockInterface {

tick(): void;


const Clock: ClockConstructor = class C implements ClockInterface {

constructor(hour: number, minute: number) { return this; }

tick() {}


const c = new Clock(1, 2);


// indexable

interface StringArray {

[index: number]: string;


interface NumberObject {

[key: string]: number;


const s: StringArray = ['a', 'b'];

const o: NumberObject = { a: 1, b: 2 };

The interface can be "opened" again, and the interface with the same name will automatically aggregate, which is very suitable for polyfill. For example, we want to extend some non-existent attributes on window:

interface Window {

g_config: {

locale: 'zh_CN' | 'en_US';



Combined method

With the basic expression, let's look at the combination method.

|And & operator

&It means that multiple contracts must be satisfied at the same time, | it means that any contract can be satisfied.

type Size = 'large' | 'normal' | 'small';

// Never can be understood as the "unit" of | operation, that is, x | never = x

type T = 1 | 2 | never; // 1 | 2

type Animal = { name: string };

type Flyable = { fly(): void };

type FlyableAnimal = Animal & Flyable; // { name: string, fly(): void }

keyof operator

interface Sizes {

large: string;

normal: string;

small: string;

x: number;


// Gets the property value of the object

type Size = keyof Sizes; // 'large' | 'normal' | 'small' | 'x'

// Reverses the type of the object property

type SizeValue = Sizes[keyof Sizes]; // string | number

// keyof any can be understood as a type that can be used as an "index"

type K = keyof any; // string | number | symbol

Abstract method

Abstract methods actually refer to "functions". Let's look at the definition of "function" in type programming.

// "Definition"

type Id<T> = T;

// "Call"

type A = Id<'a'>; // 'a'

// Parameter constraints and default values

type MakePair<T extends string, U extends number = 1> = [T, U];

type P1 = MakePair<'a', 2>; // ['a', 2]

type P2 = MakePair<'x'>; // ['x', 1]

Does it look like a function in a programming language? The input (parameter) of these "functions" is type, and after "operation", the output is type. Next, let's see what other operations can be done in the "function body" (that is, to the right of the equal sign) in addition to some basic operations.

Mapping operation (mapped)

Convert an existing type to a new type, similar to map. The new type returned is generally an object.

type MakeRecord<T extends keyof any, V> = {

[k in T]: V


type R1 = MakeRecord<1, number>; // { 1: number }

type R2 = MakeRecord<'a' | 1, string>; // { a: string, 1: string }

type TupleToObject<T extends readonly any[]> = {

[k in T[number]]: k


type O = TupleToObject<['a', 'b', 'c']>; // { a: 'a', b: 'b', c: 'c' }

Conditions - extensions

The condition type can be understood as "ternary operation", t extends u? X: y, extends can be analogized as "equal".

// Keep only string s

type OnlyString<T> = T extends string ? T : never;

type S = OnlyString<1 | 2 | true | 'a' | 'b'>; // 'a' | 'b'

// The calculation process here is roughly as follows:

// 1 | 2 | true | 'a' | 'b' -> never | never | never | 'a' | 'b'

// According to x | never = x, the final result is' a '| B'

// Get the attribute key value of the function type of the object

type FunctionPropertyNames<T> = {

[k in keyof T]: T[k] extends Function ? k : never

}[keyof T];

interface D {

id: number;

add(id: number): void;

remove(id: number): void;


type P = FunctionPropertyNames<D>; // 'add' | 'remove'

// The calculation process here is roughly as follows:

// Expand interface:

// {

// id: D['id'] extends Function ? 'id' : never, //-> false

// add: D['add'] extends Function ? 'add' : never, //-> true

// remove: D['remove'] extends Function ? 'remove' : never //-> true

// }['id' | 'add' | 'remove']

// Calculation condition type:

// {

// id: never,

//. add: 'add',

// remove: 'remove'

// }['id' | 'add' | 'remove']

// Value according to index:

// never | 'add' | 'remove'

// According to never | x = x, the final result is: 'Add' | remove '

"Deconstruction" - infer

infer can be understood as a "magnifying glass" mechanism, which can "capture" the type information embedded in various complex structures.

// Object infer, which can obtain the type of an attribute value of the object

type ObjectInfer<O> = O extends { x: infer T } ? T : never;

type T1 = ObjectInfer<{x: number}>; // number

// Array infer, which can obtain the type of array elements

type ArrayInfer<A> = A extends (infer U)[] ? U : never;

const arr = [1, 'a', true];

type T2 = ArrayInfer<typeof arr>; // number | string | boolean

// tuple infer

type TupleInfer<T> = T extends [infer A, ...(infer B)[]] ? [A, B] : never;

type T3 = TupleInfer<[string, number, boolean]>; // [string, number | boolean]

// Function infer, which can obtain the parameters and return value types of the function

type FunctionInfer<F> = F extends (...args: infer A) => infer R ? [A, R] : never;

type T4 = FunctionInfer<(a: number, b: string) => boolean>; // [[a: number, b: string], boolean]

// More other infer s

type PInfer<P> = P extends Promise<infer G> ? G : never;

const p = new Promise<number>(() => {});

type T5 = PInfer<typeof p>; // number

It can be found that the above example needs to use infer because we don't know the specific type when "defining" and need to "infer" when "calling". Infor helps us mark the types to be inferred, and finally calculate the actual types.

Nesting & recursion

In the "function body", we can actually "call the function" to form a nested and recursive mechanism.

// Take the type of the first parameter of the function

type Params<F> = F extends (...args: infer A) => any ? A : never;

type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;

type FirstParam = Head<Params<(name: string, age: number) => void>>; // string

// Recursive definition

type List<T> = {

value: T,

next: List<T>

} | null;


The article basically ends here. The content of this article may be rarely encountered in ordinary development, but it is helpful to complete your TypeScript system and broaden your horizons. If you want to do more practical exercises, it is recommended to take a look at this: https://github.com/type-chall...

Tags: Front-end Programming TypeScript

Posted by billthrill on Mon, 16 May 2022 07:30:17 +0300