资讯专栏INFORMATION COLUMN

还可以这么玩?超实用 Typescript 内置类型与自定义类型

Astrian / 1883人阅读

摘要:背景大家用过都清楚,很多时候我们需要提前声明一个类型,再将类型赋予变量。上面使用到的和都是内置的类型别名。下面给大家介绍一下常用的内置类型,以及自行拓展的类型。

背景

大家用过 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 = {
  user: "",
  id: undefined 
}
// 或者需要明确指定 key 的,可以
const model2: Partial>

这样一来,很多情况下,我们可以尽量少地写重复的类型,复用已有类型,让代码更加优雅容易维护。

上面使用到的 PartialPick 都是 typescript 内置的类型别名。下面给大家介绍一下 typescript 常用的内置类型,以及自行拓展的类型。

typescript 内置类型 Partial

将类型 T 的所有属性标记为可选属性

type Partial = {
    [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 = {
  name: "",
  vip: undefind
}
Required

与 Partial 相反,Required 将类型 T 的所有属性标记为必选属性

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

将所有属性标记为 readonly, 即不能修改

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

从 T 中过滤出属性 K

type Pick = {
    [P in K]: T[P];
};

使用场景:

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

type CoreInfo = Pick
/* 
{ 
  name: string
  email: stirng
}
*/
Record

标记对象的 key value类型

type Record = {
    [P in K]: T;
};

使用场景:

// 定义 学号(key)-账号信息(value) 的对象
const accountMap: Record = {
  10001: {
    name: "xx",
    email: "xxxxx",
    // ...
  }    
}
const user: Record<"name"|"email", string> = {
    name: "", 
    email: ""
}
// 复杂点的类型推断
function mapObject(obj: Record, f: (x: T) => U): Record

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,Omit

移除 T 中的 U 属性

type Exclude = T extends U ? never : T;

使用场景:

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

乍一看好像这个没啥卵用,但是,我们通过一番操作,之后就可以得到 Pick 的反操作:

type Omit = Pick>

type NonCoreInfo = Omit
/*
{
  age: number 
  vip: 0|1,
}
*/
Extract

Exclude 的反操作,取 T,U两者的交集属性

type Extract = T extends U ? T : never;

使用 demo:

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

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

NonNullable

排除类型 T 的 null | undefined 属性

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

使用 demo

type A = string | number | undefined 
type B = NonNullable // string | number

function f2(x: T, y: NonNullable) {
    let s1: string = x;  // Error, x 可能为 undefined
    let s2: string = y;  // Ok
}
Parameters

获取一个函数的所有参数类型

// 此处使用 infer P 将参数定为待推断类型
// T 符合函数特征时,返回参数类型,否则返回 never
type Parameters any> = T extends (...args: infer P) => any ? P : never;

使用demo:

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

type P = Parameters // [IPerson, number]

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

另一种使用场景是,快速获取未知函数的参数类型

import {somefun} from "somelib"
// 从其他库导入的一个函数,获取其参数类型
type SomeFuncParams = Parameters

// 内置函数
// [any, number?, number?]
type FillParams = Parameters
ConstructorParameters

类似于 Parameters, ConstructorParameters 获取一个类的构造函数参数

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

使用 demo:

// string | number | Date 
type DateConstrParams = ConstructorParameters
ReturnType

获取函数类型 T 的返回类型

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

使用方式和 Parameters 类似,不再赘述

InstanceType

获取一个类的返回类型

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

使用方式和 ConstructorParameters 类似,不再赘述

自定义常用类型 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 = {
  [P in keyof T]: P extends K ? any : T[P];
};


interface Test2  extends Weaken{
  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: 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 = {
    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 = Partial>

const validateRules: PartialRecord = {
   name: {required: true, trigger: `blur`} 
}

这个例子组合使用了 typescript 内置的 类型别名 PartialPartial

Unpacked

解压抽离关键类型

type Unpacked =
    T extends (infer U)[] ? U :
    T extends (...args: any[]) => infer U ? U :
    T extends Promise ? U :
    T;

type T0 = Unpacked;  // string
type T1 = Unpacked;  // string
type T2 = Unpacked<() => string>;  // string
type T3 = Unpacked>;  // string
type T4 = Unpacked[]>;  // Promise
type T5 = Unpacked[]>>;  // string
总结

事实上,基于已有的类型别名,还有新推出的 infer 待推断类型,可以探索出各种各样的复杂组合玩法,这里不再多说,大家可以慢慢探索。

感谢阅读!



本文首发于 github 博客   
如文章对你有帮助,你的 star 是对我最大的支持


插播广告:   
深圳 Shopee 长期内推
岗位:前端,后端(要转go),产品,UI,测试,安卓,IOS,运维 全都要。
薪酬福利:20K-50K

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104449.html

相关文章

  • package.json 中 你不清楚的 browser,module,main 字段优先级

    摘要:本文就来说下这几个字段的使用场景,以及同时存在这几个字段时,他们之间的优先级。当存在和这种同名不同后缀的文件时,或者是会优先加载文件的。或者优先级是通过直接执行脚本只有字段有效。 browser VS module VS main 前端开发中使用到 npm 包那可算是家常便饭,而使用到 npm 包总免不了接触到 package.json 包配置文件。 那么这里就有...

    caozhijian 评论0 收藏0
  • typescript - 一种思维方式

    摘要:怎么影响了我的思考方式对前端开发者来说,能强化了面向接口编程这一理念。使用的过程就是在加深理解的过程,确实面向接口编程天然和静态类型更为亲密。 电影《降临》中有一个观点,语言会影响人的思维方式,对于前端工程师来说,使用 typescript 开发无疑就是在尝试换一种思维方式做事情。 其实直到最近,我才开始系统的学习 typescript ,前后大概花了一个月左右的时间。在这之前,我也在...

    CKJOKER 评论0 收藏0
  • TypeScript - 一种思维方式

    摘要:怎么影响了我的思考方式对前端开发者来说,能强化了面向接口编程这一理念。使用的过程就是在加深理解的过程,确实面向接口编程天然和静态类型更为亲密。摘要: 学会TS思考方式。 原文:TypeScript - 一种思维方式 作者:zhangwang Fundebug经授权转载,版权归原作者所有。 电影《降临》中有一个观点,语言会影响人的思维方式,对于前端工程师来说,使用 typescript 开...

    noONE 评论0 收藏0
  • VS Code上手与实用插件安利

    摘要:软件跨平台支持以及,运行流畅,可谓是微软的良心之作微软有这个宇宙最强,自然也不会弱宇宙最强编辑器说到代码编辑器,我们有必要提一提还有。 原文链接:VS Code上手与超实用插件安利 工欲善其事必先利其器 Visual Studio Code (简称 VS Code / VSC) 是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号...

    miracledan 评论0 收藏0
  • React Developers的10个实用神奇工具

    摘要:但是,如果必须更改实现方法以指向不同的数据库,则单元测试将失败,因为它们是耦合逻辑的实现细节。 showImg(https://segmentfault.com/img/bVbwf0d?w=786&h=155); showImg(https://segmentfault.com/img/bVbwf8m?w=941&h=578); React是一个用于构建用户界面的JavaScript库...

    Songlcy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<