博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
还可以这么玩?超实用 Typescript 内置类型与自定义类型
阅读量:6476 次
发布时间:2019-06-23

本文共 6947 字,大约阅读时间需要 23 分钟。

背景

大家用过 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>

从 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<K, T>

标记对象的 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 为 numberconst lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }复制代码

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

移除 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<T, U>

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 | numberfunction f2
(x: T, y: NonNullable
) { let s1: string = x; // Error, x 可能为 undefined let s2: string = y; // Ok}复制代码

Parameters

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

// 此处使用 infer P 将参数定为待推断类型// T 符合函数特征时,返回参数类型,否则返回 nevertype 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<T>, 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<T> 类似,不再赘述

InstanceType

获取一个类的返回类型

type InstanceType
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
= { [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.4type 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 内置的 类型别名 PartialRecord

Unpacked

解压抽离关键类型

type Unpacked
= T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise
? U : T;type T0 = Unpacked
; // stringtype T1 = Unpacked
; // stringtype T2 = Unpacked<() => string>; // stringtype T3 = Unpacked
>; // stringtype T4 = Unpacked
[]>; // Promise
type T5 = Unpacked
[]>>; // string复制代码

总结

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

感谢阅读!

本文首发于

如文章对你有帮助, 是对我最大的支持

插播广告:

深圳 Shopee 长期内推
岗位:前端,后端(要转go),产品,UI,测试,安卓,IOS,运维 全都要。
薪酬福利:20K-50K?,7点下班?(划重点),免费水果?,免费晚餐?,15天年假?,14天带薪病假。
简历发邮箱:chenweiyu6909@gmail.com 或者加我微信:cwy13920,实时反馈面试进度哦。

转载地址:http://ovqko.baihongyu.com/

你可能感兴趣的文章
彻底学会使用epoll(一)——ET模式实现分析
查看>>
路由器的密码恢复
查看>>
【Android 基础】Android中全屏或者取消标题栏
查看>>
Xilinx 常用模块汇总(verilog)【03】
查看>>
脱离标准文档流(2)---定位
查看>>
IO流之字符流
查看>>
集合异常之List接口
查看>>
Softmax回归
查看>>
紫书 习题11-11 UVa 1644 (并查集)
查看>>
App工程结构搭建:几种常见Android代码架构分析
查看>>
使用openssl进行证书格式转换
查看>>
ZOJ 3777 Problem Arrangement
查看>>
虚拟机类加载机制
查看>>
Callable和Future
查看>>
installshield12如何改变默认安装目录
查看>>
少用数字来作为参数标识含义
查看>>
ScrollView中嵌套ListView
查看>>
JAVA虚拟机05--面试必问之JVM原理
查看>>
Algs4-2.3.1如何切分数组
查看>>
观察者模式
查看>>