background
As you all know with Typescript, many times we need to declare a type in advance and assign it to a variable.
For example, in business, we need to render a table and often define:
interface Row { user: string email: string id: number vip: boolean // ... } const tableDatas: Row[] = [] // ...
Sometimes we also need a search form for a table, one or two of which we need, and a classmate who has just touched typescript may write this right away:
interface SearchModel { user?: string id?: number } const model: SearchModel = { user: '', id: undefined }
There is a problem with this writing. If you change the id type to string later, we need to change it in two places. If you are not careful, you may forget to change another one.So some people would write like this:
interface SearchModel { user?: Row['user'] id?: Row['id'] }
This is a workaround, but in fact, we've already defined the Row type, which can actually be reused more elegantly:
const model: Partial<Row> = { user: '', id: undefined } // Or if you need to specify a key explicitly, you can const model2: Partial<Pick<Row, 'user'|'id'>>
This way, in many cases, we can write as few duplicate types as possible, reuse existing ones, and make the code more elegant and easy to maintain.
Partial and Pick used above are built-in type aliases for typescript.Here's a look at the built-in types typescript commonly uses, as well as the self-expanding ones.
Typeescript built-in type
Partial<T>
Mark all attributes of type T as optional
type Partial<T> = { [P in keyof T]?: T[P]; };
Use scenarios:
// Account Properties interface AccountInfo { name: string email: string age: number vip: 0|1 // 1 is vip, 0 is non-VIP } // When we need to render an account form, we need to define const accountList: AccountInfo[] = [] // But when we need to query and filter account information, we need to go through the form. // But it's clear that we may not necessarily need to use all the attributes to search, so we can define const model: Partial<AccountInfo> = { name: '', vip: undefind }
Required<T>
In contrast to Partial, Required marks all attributes of type T as required
type Required<T> = { [P in keyof T]-?: T[P]; };
Readonly<T>
Mark all properties readonly, that is, they cannot be modified
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
Pick<T, K>
Filter attribute K from T
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
Use scenarios:
interface AccountInfo { name: string email: string age: number vip?: 0|1 // 1 is vip, 0 is non-VIP } type CoreInfo = Pick<AccountInfo, 'name' | 'email'> /* { name: string email: stirng } */
Record<K, T>
key value type of markup object
type Record<K extends keyof any, T> = { [P in K]: T; };
Use scenarios:
// Object that defines the key - account information (value) const accountMap: Record<number, AccountInfo> = { 10001: { name: 'xx', email: 'xxxxx', // ... } } const user: Record<'name'|'email', string> = { name: '', email: '' }
// Type inference of complex points function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> const names = { foo: "hello", bar: "world", baz: "bye" }; // Here we infer that K, T is string, U is number const lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }
Exclude<T, U>,Omit<T, K>
Remove the U attribute from T
type Exclude<T, U> = T extends U ? never : T;
Use scenarios:
// 'a' | 'd' type A = Exclude<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >
At first glance, it looks like this is useless, but after a few operations, we can get a counteraction from Pick:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> type NonCoreInfo = Omit<AccountInfo, 'name' | 'email'> /* { age: number vip: 0|1, } */
Extract<T, U>
Reverse operation of Exclude, take the intersection property of T and U
type Extract<T, U> = T extends U ? T : never;
Using demo:
// 'b'|'c' type A = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >
It seems useless, but it's really useless. I should be ignorant and have not yet discovered its use.
NonNullable<T>
Exclude null | undefined attribute of type T
type NonNullable<T> = T extends null | undefined ? never : T;
Using demo
type A = string | number | undefined type B = NonNullable<A> // string | number function f2<T extends string | undefined>(x: T, y: NonNullable<T>) { let s1: string = x; // Error, x may be undefined let s2: string = y; // Ok }
Parameters<T>
Gets all the parameter types of a function
// Here infer P is used to set the parameter as the type to be inferred // Returns the parameter type when T conforms to the function characteristics, otherwise returns never type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Use demo:
interface IFunc { (person: IPerson, count: number): boolean } type P = Parameters<IFunc> // [IPerson, number] const person01: P[0] = { // ... }
Another scenario is to quickly get the parameter type of an unknown function
import {somefun} from 'somelib' // A function imported from another library to get its parameter type type SomeFuncParams = Parameters<typeof somefun> // Built-in function // [any, number?, number?] type FillParams = Parameters<typeof Array.prototype.fill>
ConstructorParameters<T>
Similar to Parameters <T>, ConstructorParameters takes constructor parameters for a class
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
Use demo:
// string | number | Date type DateConstrParams = ConstructorParameters<typeof Date>
ReturnType<T>
Gets the return type of function type T
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
Similar to Parameters <T>, no more details
InstanceType<T>
Gets the return type of a class
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
Similar to ConstructorParameters <T> in usage, no more details
Customize Common Types
Weaken
Using typescript sometimes requires overriding an attribute of the interface provided by a library, but overriding the interface can lead to conflicts:
interface Test { name: string say(word: string): string } interface Test2 extends Test{ name: Test['name'] | number } // error: Type 'string | number' is not assignable to type 'string'.
Then we can curve the nation with some type s to meet our needs:
// The principle is to set all K attributes of type T to any, // Then customize the type of the K attribute, // Since any type can be assigned to any, there is no conflict type Weaken<T, K extends keyof T> = { [P in keyof T]: P extends K ? any : T[P]; }; interface Test2 extends Weaken<Test, 'name'>{ name: Test['name'] | number } // ok
Array to union
Sometimes needed
const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const; // TS 3.4 type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs'] type Suit = SuitTuple[number]; // union type : 'hearts' | 'diamonds' | 'spades' | 'clubs'
Generate union from enum
-
key value union of enum
enum Weekday { Mon = 1 Tue = 2 Wed = 3 } type WeekdayName = keyof typeof Weekday // 'Mon' | 'Tue' | 'Wed'
-
enum cannot implement value-union, but can object union with value
const lit = <V extends keyof any>(v: V) => v; const Weekday = { MONDAY: lit(1), TUESDAY: lit(2), WEDNESDAY: lit(3) } type Weekday = (typeof Weekday)[keyof typeof Weekday] // 1|2|3
PartialRecord
Earlier we talked about the Record type, which we'll use often
interface Model { name: string email: string id: number age: number } // Define Check Rules for Forms const validateRules: Record<keyof Model, Validator> = { name: {required: true, trigger: `blur`}, id: {required: true, trigger: `blur`}, email: {required: true, message: `...`}, // error: Property age is missing in type... }
There is a problem here that the key value of validateRules must match all Model s, but in fact our form may only have one or two of them, so we need to:
type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>> const validateRules: PartialRecord<keyof Model, Validator> = { name: {required: true, trigger: `blur`} }
This example combines the built-in type aliases Partial and Artial of typescript.
Unpacked
Decompression Extraction Key Types
type Unpacked<T> = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T; type T0 = Unpacked<string>; // string type T1 = Unpacked<string[]>; // string type T2 = Unpacked<() => string>; // string type T3 = Unpacked<Promise<string>>; // string type T4 = Unpacked<Promise<string>[]>; // Promise<string> type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
summary
In fact, based on the existing type aliases and the new infer inference types, you can explore a wide variety of complex combinations of play, not to mention here, but you can explore them slowly.
Thanks for reading!
This article was first published in github blog
If the article helps you, Your star Is my greatest support
Advertising Interviews:
SHENZHEN SHOPE LONG-TERM INTRODUCTION
Job: Front end, Back end (to go), Product, UI, Test, Android, IOS, Operations and Maintenance.
Compensation benefits: 20K-50K, 7 o'clock off(priority), free fruit, free dinner, 15 days annual leave, 14 days paid sick leave. Click here for details
Resume email: chenweiyu6909@gmail.com
Or add me WeChat: cwy13920, real-time feedback on interview progress.