摘要:背景大家用过都清楚,很多时候我们需要提前声明一个类型,再将类型赋予变量。上面使用到的和都是内置的类型别名。下面给大家介绍一下常用的内置类型,以及自行拓展的类型。
背景
大家用过 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
>
这样一来,很多情况下,我们可以尽量少地写重复的类型,复用已有类型,让代码更加优雅容易维护。
上面使用到的 Partial 和 Pick 都是 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: PartialRequired= { name: "", vip: undefind }
与 Partial 相反,Required 将类型 T 的所有属性标记为必选属性
type RequiredReadonly= { [P in keyof T]-?: T[P]; };
将所有属性标记为 readonly, 即不能修改
type ReadonlyPick= { readonly [P in keyof T]: T[P]; };
从 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 = PickRecord/* { name: string email: stirng } */
标记对象的 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 mapObjectExclude(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 }
移除 T 中的 U 属性
type Exclude= T extends U ? never : T;
使用场景:
// "a" | "d" type A = Exclude<"a"|"b"|"c"|"d" ,"b"|"c"|"e" >
乍一看好像这个没啥卵用,但是,我们通过一番操作,之后就可以得到 Pick 的反操作:
type OmitExtract= Pick > type NonCoreInfo = Omit /* { age: number vip: 0|1, } */
Exclude 的反操作,取 T,U两者的交集属性
type Extract= T extends U ? T : never;
使用 demo:
// "b"|"c" type A = Extract<"a"|"b"|"c"|"d" ,"b"|"c"|"e" >
这个看起来没啥用,实际上还真没啥卵用,应该是我才疏学浅,还没发掘到其用途。
排除类型 T 的 null | undefined 属性
type NonNullable= T extends null | undefined ? never : T;
使用 demo
type A = string | number | undefined type B = NonNullable // string | number function f2Parameters(x: T, y: NonNullable ) { let s1: string = x; // Error, x 可能为 undefined let s2: string = y; // Ok }
获取一个函数的所有参数类型
// 此处使用 infer P 将参数定为待推断类型 // T 符合函数特征时,返回参数类型,否则返回 never type Parametersany> = 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 = ParametersConstructorParameters// 内置函数 // [any, number?, number?] type FillParams = Parameters
类似于 Parameters
type ConstructorParametersany> = T extends new (...args: infer P) => any ? P : never;
使用 demo:
// string | number | Date type DateConstrParams = ConstructorParametersReturnType
获取函数类型 T 的返回类型
type ReturnTypeany> = T extends (...args: any) => infer R ? R : any;
使用方式和 Parameters
获取一个类的返回类型
type InstanceTypeany> = T extends new (...args: any) => infer R ? R : any;
使用方式和 ConstructorParameters
使用 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数组 转换 成 union= { [P in keyof T]: P extends K ? any : T[P]; }; interface Test2 extends Weaken { name: Test["name"] | number } // ok
有时候需要
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 =PartialRecord(v: V) => v; const Weekday = { MONDAY: lit(1), TUESDAY: lit(2), WEDNESDAY: lit(3) } type Weekday = (typeof Weekday)[keyof typeof Weekday] // 1|2|3
前面我们讲到了 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 内置的 类型别名 Partial 和 Partial。
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
摘要:本文就来说下这几个字段的使用场景,以及同时存在这几个字段时,他们之间的优先级。当存在和这种同名不同后缀的文件时,或者是会优先加载文件的。或者优先级是通过直接执行脚本只有字段有效。 browser VS module VS main 前端开发中使用到 npm 包那可算是家常便饭,而使用到 npm 包总免不了接触到 package.json 包配置文件。 那么这里就有...
摘要:怎么影响了我的思考方式对前端开发者来说,能强化了面向接口编程这一理念。使用的过程就是在加深理解的过程,确实面向接口编程天然和静态类型更为亲密。 电影《降临》中有一个观点,语言会影响人的思维方式,对于前端工程师来说,使用 typescript 开发无疑就是在尝试换一种思维方式做事情。 其实直到最近,我才开始系统的学习 typescript ,前后大概花了一个月左右的时间。在这之前,我也在...
摘要:怎么影响了我的思考方式对前端开发者来说,能强化了面向接口编程这一理念。使用的过程就是在加深理解的过程,确实面向接口编程天然和静态类型更为亲密。摘要: 学会TS思考方式。 原文:TypeScript - 一种思维方式 作者:zhangwang Fundebug经授权转载,版权归原作者所有。 电影《降临》中有一个观点,语言会影响人的思维方式,对于前端工程师来说,使用 typescript 开...
摘要:软件跨平台支持以及,运行流畅,可谓是微软的良心之作微软有这个宇宙最强,自然也不会弱宇宙最强编辑器说到代码编辑器,我们有必要提一提还有。 原文链接:VS Code上手与超实用插件安利 工欲善其事必先利其器 Visual Studio Code (简称 VS Code / VSC) 是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号...
摘要:但是,如果必须更改实现方法以指向不同的数据库,则单元测试将失败,因为它们是耦合逻辑的实现细节。 showImg(https://segmentfault.com/img/bVbwf0d?w=786&h=155); showImg(https://segmentfault.com/img/bVbwf8m?w=941&h=578); React是一个用于构建用户界面的JavaScript库...
阅读 1918·2021-11-23 09:51
阅读 815·2021-11-19 09:40
阅读 790·2021-10-27 14:20
阅读 4868·2021-10-09 09:52
阅读 3247·2021-10-09 09:44
阅读 1700·2021-10-08 10:05
阅读 4911·2021-09-09 11:47
阅读 3458·2019-08-30 12:47