Can you play like this?Ultra-functional Typescript built-in and custom types

Keywords: Javascript TypeScript Attribute github Android

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.

Posted by purefusion on Wed, 05 Jun 2019 09:05:11 -0700