玩转TypeScript工具类型(上)
联系我们:有道技术团队助手:ydtech01 / 邮箱:mailto:ydtech@rd.netease.com
随着前端项目的规模不断变大,多人协同开发已经成为了前端开发的标配,随之而来的就是 TypeScript 被越来越多的项目所使用,这种变化并不是对技术的盲目追求,而是业务驱动下的技术进步,TypeScript 通过对原生 JavaScript 提供强类型加持,在很大程度上提升了代码质量,大大降低了多人协同场景下不同模块接口相互调用可能出现的隐性 bug。本系列分享来源于我日常开发中对 TypeScript 工具类型的一些学习和使用体会,本系列文章分为上中下三篇,通过本系列分享,希望可以达到如下几个目的:
- 从源码的角度理解每一种工具类型的实现机制
- 通过一两个简单的例子了解每一种工具类型的基本用法
- 与此同时加深对 TypeScript 的理解
- 最终实现可以在实际工作中举一反三
1.1 源码解读
Partial
type Partial = {
[P in keyof T]?: T[P];
};
这里需要关注四个点:
:这是目标类型,也就是我们要做处理的类型,类型不确定,所以用泛型 T 表示[P in keyof T]
:keyof T返回 T 类型的所有键组成的一个类型,in 可以按照js中的for..in遍历去理解,后续对keyof有更详细的说明?
:可选,把返回类型的所有属性都转为可选类型- 返回的是一个新类型,这个新类型来源于 T,并且和 T 在属性上有一种继承关系,在第2小节对
Required
的说明中会验证这一点
Partial
类型的作用就是返回一个新类型,这个新类型和目标类型 T 拥有相同的属性,但所有属性都是可选的。1.2 实战用法
场景说明:在实际的业务开发中,经常会遇到需要对一个数据对象做整体或者局部更新的需求,这里就可以用到
Partial
interface DataModel {
name: string
age: number
address: string
}let store: DataModel = {
name: '',
age: 0,
address: ''
}function updateStore (
store: DataModel,
payload: Partial
):DataModel {
return {
...store,
...payload
}
}store = updateStore(store, {
name: 'lpp',
age: 18
})
亲手试一试1.3 补充
这里增加一个对 keyof 的说明,通过一段代码来理解一下:
interface Person {
name: string;
age: number;
location: string;
}type K1 = keyof Person;
// "name" | "age" | "location"
type K2 = keyof Person[];
// "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };
// stringconst person: Person = {
name: '',
age: 0,
location: ''
}type k11 = keyof typeof person;
// "name" | "age" | "location"
【玩转TypeScript工具类型(上)】参考一:官方文档说明二、 Required
参考二:What does “keyof typeof” mean in TypeScript?
type Required = {
[P in keyof T]-?: T[P];
};
这个类型的作用就是把类型 T 中的所有属性都转为可选属性。
这里源码中使用了一个 -? 来标注属性为必填的属性,那么这个 -? 是否是必须的呢?因为我们理解的可选属性是用 ? 明确标识的才是可选的,如果我们把-? 去掉,为什么就无法实现 Required 的效果了呢?我们先写一个 MyRequired
type MyRequired = {
[P in keyof T]: T[P];
};
interface Props {
a?: number;
b?: string;
}const obj: MyRequired = {
a: 5
};
亲手试一试上面的代码是没有类型错误的,为什么呢?因为如果只是[P in keyof T] ,P 中的属性会保留它自身在 T中的可选性。即之前如果是必填的,在新类型中还是必填的,如果是选填的同理。有点类似一种
“继承关系”
。所以使用 -? 来清除可选性实现 Required 。当然,+? 也是有效的,所以参考Partial
2.2 实战用法
Required
interface Props {
a?: string
b?: string
c?: string
}// 仅保留b,c属性并转为必填
type NewProps1 = Required>// 需要保留Props的所有属性,但是b,c需要必填
type NewProps2 = Partial & Required>const obj: NewProps2 = {
b: '1',
c: '2'
}
亲手试一试三、 ReadOnly
type Readonly = {
readonly [P in keyof T]: T[P];
};
将类型 T 中包含的属性设置为readonly,并返回一个新类型。
readonly,顾名思义表示只读,初始化后就不能再修改值。这个类型可以配合 javascript 的 const 关键字实现引用类型属性值为常量的目的。
这个类型有一个局限性,就是只能设置子属性为只读,如果子属性还是一个引用类型,那对孙属性是不起作用的,那么有没有什么办法可以实现递归把所有引用都设置成只读的呢?
欢迎大家留言讨论。
3.2 实战用法
interface Person {
name: string
age: number
}const person: Readonly = {
name: 'lpp',
age: 18
}person.age = 20;
// 无法分配到 "age" ,因为它是只读属性。ts(2540)
如果没有 readonly ,在 javascript 中,如果给 const 变量赋值唯一个引用类型,比如一个对象,是可以修改属性值的,不能修改的是变量中存储的引用,如果要实现对象属性值的不可变,在 javascript 中可以使用 Object.freeze 。
function freeze(obj: Type): Readonly;
四、 Record
4.1 源码解读
type Record = {
[P in K]: T;
};
这里我们看到对 K 的类型定义使用的是 keyof any。这里 keyof any string | number | symbol,如下所示:
type a = keyof any;
// 等价于
type a = string | number | symbol;
4.2 实战用法
// 简单的限定键和值的类型
type Obj1 = Record// 基于其他类型生成新的类型
type FruitTypes = 'apple' | 'banana' | 'pear'interface FruitInfo {
name: FruitTypes
price: number
}type Fruits = Partial>const fruits: Fruits = {
apple: {
name: 'apple',
price: 10
}
}
亲手试一试五、Pick
5.1 源码解读
type Pick = {
[P in K]: T[P];
};
K extends keyof ,表示 K 需要是 keyof T 的子集。返回的类型的键需要满足[P in K],值类型满足T[P]。
5.2 实战用法
interface Person {
name: string
age: number
id: string
}// 幼儿没有id
type Toddler = Pick
六、 Omit
6.1 源码解读
type Omit = Pick>;
type Exclude = T extends U ? never : T;
因为 Omit 依赖了 Exclude ,所以这里把 Exclude 的类型源码一起贴在这里。
Exclude
这里先不讲 Exclude 是如何实现的,只需要知道功能即可。
所以可以把 Exclude
6.2 实战用法
interface Person {
name: string
age: number
id: string
work: string
address: string
girlFriend: number
}// 没工作的人
type PersonNoWork = Omit// 没住址的人
type PersonNoAddress = Omit// 没女朋友的人
type PersonNoGirlFriend =Omit
七、练习题 如何实现一个工具类型 SelectRequired
interface Props {
a?: string
b?: string
c?: string
d: string
}type NewProps = SelectRequired;
// { a?: string, b: string, c: string, d: string }
答案点这里八、下回预告 在下一篇《玩转TypeScript工具类型(中)》里,将会包括如下内容,敬请期待:
- 必读:extends条件运算符
- Exclude
- Extract
- NonNullable
- 必读:tuple type元组类型
- Parameters
- ConstructorParameters
- ReturnType
- InstanceType
推荐阅读
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 孩子不是实现父母欲望的工具——林哈夫
- 最有效的时间管理工具(赢效率手册和总结笔记)
- 工具|后天就是七夕节,你准备好了吗(送上几个七夕代码,展示你技能的时候到了!)
- 工具分享|5个有趣好玩的网站,拒绝无聊!
- typeScript入门基础介绍
- 工具人的一天
- 炒股知识(超级短线操作的秘籍|炒股知识:超级短线操作的秘籍 玩转股市)
- jvm常见分析工具
- docker镜像探索----dive工具