还能够这么玩?超有用 Typescript 内置范例与自定义范例

背景

人人用过 Typescript 都清晰,许多时刻我们须要提早声明一个范例,再将范例给予变量。

比方在营业中,我们须要衬着一个表格,每每须要定义:

interface Row {
  user: string
  email: string
  id: number
  vip: boolean
  // ...
}

const tableDatas: Row[] = []
// ...

有时刻我们也须要表格对应的搜刮表单,须要个中一两个搜刮项,假如刚打仗 typescript 的同砚能够会马上如许写:

interface SearchModel {
  user?: string
  id?: number 
}  
const model: SearchModel = {
  user: '',
  id: undefined 
}

如许写会涌现一个题目,假如背面id 范例要改成 string,我们须要改 2 处处所,不小心的话能够就会忘了改别的一处。所以,有些人会如许写:

interface SearchModel {
  user?: Row['user']
  id?: Row['id']
} 

这固然是一个解决方法,但事实上,我们前面已定义了 Row 范例,这实际上是能够更文雅地复用的:

const model: Partial<Row> = {
  user: '',
  id: undefined 
}
// 或许须要明白指定 key 的,能够
const model2: Partial<Pick<Row, 'user'|'id'>>

如许一来,许多情况下,我们能够只管少地写反复的范例,复用已有范例,让代码越发文雅轻易保护。

上面运用到的 PartialPick 都是 typescript 内置的范例别号。下面给人人引见一下 typescript 经常运用的内置范例,以及自行拓展的范例。

typescript 内置范例

Partial<T>

将范例 T 的一切属性标记为可选属性

type Partial<T> = {
    [P in keyof T]?: T[P];
};

运用场景:

// 账号属性
interface AccountInfo {
    name: string 
    email: string 
    age: number 
    vip: 0|1 // 1 是vip ,0 黑白vip
}

// 当我们须要衬着一个账号表格时,我们须要定义
const accountList: AccountInfo[] = []

// 但当我们须要查询过滤账号信息,须要经由过程表单,
// 但显著我们能够并不一定须要用到一切属性举行搜刮,此时能够定义
const model: Partial<AccountInfo> = {
  name: '',
  vip: undefind
}

Required<T>

与 Partial 相反,Required 将范例 T 的一切属性标记为必选属性

type Required<T> = {
    [P in keyof T]-?: T[P];
};

Readonly<T>

将一切属性标记为 readonly, 即不能修正

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

Pick<T, K>

从 T 中过滤出属性 K

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

运用场景:

interface AccountInfo {
  name: string 
  email: string 
  age: number 
  vip?: 0|1 // 1 是vip ,0 黑白vip
}

type CoreInfo = Pick<AccountInfo, 'name' | 'email'>
/* 
{ 
  name: string
  email: stirng
}
*/

Record<K, T>

标记对象的 key value范例

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

运用场景:

// 定义 学号(key)-账号信息(value) 的对象
const accountMap: Record<number, AccountInfo> = {
  10001: {
    name: 'xx',
    email: 'xxxxx',
    // ...
  }    
}
const user: Record<'name'|'email', string> = {
    name: '', 
    email: ''
}
// 庞杂点的范例揣摸
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" };
// 此处揣摸 K, T 值为 string , U 为 number
const lengths = mapObject(names, s => s.length);  // { foo: number, bar: number, baz: number }

Exclude<T, U>,Omit<T, K>

移除 T 中的 U 属性

type Exclude<T, U> = T extends U ? never : T;

运用场景:

// 'a' | 'd'
type A = Exclude<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >  

乍一看彷佛这个没啥卵用,然则,我们经由过程一番操纵,以后就能够获得 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>

Exclude 的反操纵,取 T,U二者的交集属性

type Extract<T, U> = T extends U ? T : never;

运用 demo:

// 'b'|'c'
type A = Extract<'a'|'b'|'c'|'d' ,'b'|'c'|'e' >  

这个看起来没啥用,实际上还真没啥卵用,应该是我才疏学浅,还没发掘到其用处。

NonNullable<T>

消除范例 T 的 null | undefined 属性

type NonNullable<T> = T extends null | undefined ? never : T;

运用 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 能够为 undefined
    let s2: string = y;  // Ok
}

Parameters<T>

猎取一个函数的一切参数范例

// 此处运用 infer P 将参数定为待揣摸范例
// T 相符函数特性时,返回参数范例,不然返回 never
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

运用demo:

interface IFunc {
  (person: IPerson, count: number): boolean
}

type P = Parameters<IFunc> // [IPerson, number]

const person01: P[0] = {
  // ...
}

另一种运用场景是,疾速猎取未知函数的参数范例

import {somefun} from 'somelib'
// 从其他库导入的一个函数,猎取其参数范例
type SomeFuncParams = Parameters<typeof somefun>

// 内置函数
// [any, number?, number?]
type FillParams = Parameters<typeof Array.prototype.fill>

ConstructorParameters<T>

相似于 Parameters<T>, ConstructorParameters 猎取一个类的组织函数参数

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

运用 demo:

// string | number | Date 
type DateConstrParams = ConstructorParameters<typeof Date>

ReturnType<T>

猎取函数范例 T 的返回范例

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

运用体式格局和 Parameters<T> 相似,不再赘述

InstanceType<T>

猎取一个类的返回范例

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

运用体式格局和 ConstructorParameters<T> 相似,不再赘述

自定义经常运用范例

Weaken

运用 typescript 有时刻须要重写一个库供应的 interface 的某个属性,然则重写 interface 有能够会致使争执:

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'.

那末能够经由过程一些 type 来曲线救国完成我们的需求:

// 道理是,将 范例 T 的一切 K 属性置为 any,
// 然后自定义 K 属性的范例,
// 因为任何范例都能够给予 any,所以不会发生争执
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

数组 转换 成 union

有时刻须要

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'

依据 enum 天生 union

  • enum 的 key 值 union

    enum Weekday {
      Mon = 1
      Tue = 2
      Wed = 3
    }
    type WeekdayName = keyof typeof Weekday // 'Mon' | 'Tue' | 'Wed'
  • enum 没法完成value-union , 但能够 object 的 value 值 union

    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

前面我们讲到了 Record 范例,我们会经常运用到

interface Model {
    name: string
    email: string
    id: number
    age: number
}

// 定义表单的校验划定规矩
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...
}

这里涌现了一个题目,validateRules 的 key 值必需和 Model 悉数婚配,缺一不可,但实际上我们的表单能够只要个中的一两项,这时刻我们就须要:

type PartialRecord<K extends keyof any, T> = Partial<Record<K, T>>

const validateRules: PartialRecord<keyof Model, Validator> = {
   name: {required: true, trigger: `blur`} 
}

这个例子组合运用了 typescript 内置的 范例别号 PartialPartial

Unpacked

解压抽离症结范例

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

总结

事实上,基于已有的范例别号,另有新推出的 infer 待揣摸范例,能够探究出林林总总的庞杂组合弄法,这里不再多说,人人能够逐步探究。

谢谢浏览!

本文首发于 github 博客

如文章对你有协助,你的 star 是对我最大的支撑

插播广告:

深圳 Shopee 长期内推

岗亭:前端,后端(要转go),产物,UI,测试,安卓,IOS,运维 全都要。

薪酬福利:20K-50K😳,7点放工😏(划重点),免费生果😍,免费晚饭😊,15天年假👏,14天带薪病假。
点击检察概况

简历发邮箱:chenweiyu6909@gmail.com

或许加我微信:cwy13920,及时反应口试进度哦。

    原文作者:微雨微语
    原文地址: https://segmentfault.com/a/1190000019402237
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞