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); c.tick(); // 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;
Epilogue
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...