Typescript内置类型与自定义类型

更新日期: 2019-06-08 阅读: 7.8k 标签: 类型

背景

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

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

上面使用到的 Partial 和 Pick 都是 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 内置的 类型别名 Partial 和 Partial。

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 博客 


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/3576

相关推荐

Js如何将String转化为Int

字符串到整型的一个转换,在面试过程中或者在工作中都会频繁遇到,那么string到js怎么转换呢 ?parseInt方法在format\'00\'开头的数字时会当作2进制转10进制的方法进行转换

Js实现base64,file和blob相互转换

JavaScript实现base64,file和blob相互转换:base64转为Blob;Blob转为base64;base64转换为file;js图片转换为base64;在Java中base64和File相互转换

Js的6种基本数据类型

在JS中一共有六种数据类型 String:字符串 Number:数值 Boolean:布尔值 Null:空值 Undefined:未定义 Object:对象 ,其中String,Number,Boolean,Null,Undefined属于基本数据类型而Object属于引用数据类型

JavaScript判断字符串是否为数字类型

JavaScript中有Number.isInteger可以判断一个字符串是否为整数。不过目前JS没有内置的函数来判断一个数字是否为包含小数的数字:

TypeScript never 类型

在类型理论(数学逻辑中的一种理论)中, 底部类型 是没有值的类型。也称为零或空类型,有时用 falsum(⊥)表示。 数学理论与计算机的发展是相辅相成的,底部类型在计算机科学中也有一定的应用场景。

JS数据类型转换表

下表显示了将不同的JavaScript值转换为Number,String和Boolean的结果:注意:引号(\\\"\\\")中的值表示字符串值。在红色的值是程序员可能不希望被转换为的值。

Js的typeof返回哪些数据类型?

JavaScript中的数据类型:值类型(基本类型):字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。引用数据类型:对象(Object)、数组(Array)、函数(Function)。

为你的 JavaScript 项目添加智能提示和类型检查

近在做项目代码重构,其中有一个要求是为代码添加智能提示和类型检查。智能提示,英文为 IntelliSense,能为开发者提供代码智能补全、悬浮提示、跳转定义等功能,帮助其正确并且快速完成编码。

JS中的布尔 数字 字符串

JS中所有的值都可以转换成布尔类型 使用Boolean()或者 !!(两个感叹号),JS中所有的值都可以转换成数字类型,使用Number()或+。数字类型转换场景目的只有一个,用于计算,将后台传递的数据,从字符串转换为数字并参与计算

js中关于带数字类型参数传参丢失首位数字0问题

最近在项目中遇到一个问题,js中传带有数字的参数时,如果参数开头有数字0,会把0给去掉,由于js对数据类型没有一个具体的声明,传入的数值有可能被其默认当中数字,而将多余的0给自动去除了。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!