深入typescript之‘可选的’和‘必要的’
Posted 恪愚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入typescript之‘可选的’和‘必要的’相关的知识,希望对你有一定的参考价值。
随着前端项目的规模不断变大,多人协同开发越来越受到所有公司的欢迎。随之而来的就是 TypeScript 被越来越多的项目所使用,这种变化并不是对技术的盲目追求,而是业务驱动下的技术进步,TypeScript 通过对原生 javascript 提供强类型加持,在很大程度上提升了代码质量,大大降低了多人协同场景下不同模块接口相互调用可能出现的隐性 bug。
比如:如果一个接口的某个属性是非必要的,那么你可以使用可选?:
,但是
-
如果一个接口的属性在有的继承中是必要的,而在另一些时候是非必要的呢?
-
如果一个接口中的某一个/些属性是不能要的呢?
Partial:可选
1 从源码看,它是什么
Partial<Type>
类型的源码如下所示:
type Partial<T> = {
[P in keyof T]?: T[P];
};
这里需要关注四个点:
<T>
:这是目标类型,也就是我们要做处理的类型,类型不确定,所以用泛型 T 表示[P in keyof T] :keyof T
返回 T 类型的所有键组成的一个类型,in 可以按照js中的for…in遍历去理解,后续对keyof有更详细的说明?:
可选,把返回类型的所有属性都转为可选类型- 返回的是一个新类型,这个新类型来源于 T,并且和 T 在属性上有一种继承关系!
基于对源码的理解,就可以很好的理解Partial<Type>
类型的作用就是返回一个新类型,这个新类型和目标类型 T 拥有相同的属性,但所有属性都是可选的。
2 从实战看,它怎么用
场景说明:在实际的业务开发中,经常会遇到需要对一个数据对象做整体或者局部更新的需求,这里就可以用到Partial<Type>
interface DataModel {
name: string
age: number
address: string
}
let store: DataModel = {
name: '',
age: 0,
address: ''
}
function updateStore (
store: DataModel,
payload: Partial<DataModel>
):DataModel {
return {
...store,
...payload
}
}
store = updateStore(store, {
name: 'lpp',
age: 18
})
3 补充
之前曾经专门讲过 keyof 修饰符 —— 它返回一个联合类型。这里增加一个对 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 }; // string
const person: Person = {
name: '',
age: 0,
location: ''
}
type k11 = keyof typeof person; // "name" | "age" | "location"
Required :必要的
1 从源码看,它是什么
type Required<T> = {
[P in keyof T]-?: T[P];
};
这个类型的作用就是把类型 T 中的所有属性都转为可选属性。
这里源码中使用了一个 -?
来标注属性为必填的属性,那么这个 -?
是否是必须的呢?因为我们理解的可选属性是用 ?
明确标识的才是可选的。
如果我们把 -?
去掉,为什么就无法实现 Required 的效果了呢?我们先自己实现一个 MyRequired<T>
,如下所示:
type MyRequired<T> = {
[P in keyof T]: T[P];
};
interface Props {
a?: number;
b?: string;
}
const obj: MyRequired<Props> = {
a: 5
};
上面的代码是没有类型错误的,因为如果只是[P in keyof T]
,P 中的属性会保留它自身在 T 中的可选性。即之前如果是必填的,在新类型中还是必填的,如果是选填的同理。有点类似一种“继承关系”。所以使用 -?
来清除可选性实现 Required 。
2 从实战看,它怎么用
Required<T>
会将传入的T类型的所有属性都转为必要的。所以最常见的用法就是做诸如此类的转换,但是如果只是想把 T 类型的某些属性转为必填并把这些属性返回成一个新类型?我们可以这么做:
interface Props {
a?: string
b?: string
c?: string
}
// 仅保留b,c属性并转为必填
type NewProps1 = Required<Pick<Props, 'b' | 'c'>>
// 需要保留Props的所有属性,但是b,c需要必填
type NewProps2 = Partial<Props> & Required<Pick<Props, 'b' | 'c'>>
const obj: NewProps2 = {
b: '1',
c: '2'
}
Omit<Type, Keys>:忽略
构造一个类型,这个类型包含类型 Type中除了 Keys 之外的其余属性。 Keys是一个字符串或者字符串并集。
1 从源码看,它是什么
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Exclude<T, U> = T extends U ? never : T;
因为 Omit 依赖了 Exclude ,所以这里把 Exclude 的类型源码一起贴在这里。Exclude<T, U>
的作用是从 T 中排除那些可以分配给 U 的类型。
Exclude 的实现原理也很简单:在typescript中,never修饰的属性永远“不可到达”。但这里只需要知道功能即可。
有人疑惑于Exclude, T似乎比U的范围大一些 ? 也正是因为这样,所以T中的属性总有不是从U中extends过来的, 我们需要的也是这些属性 !
所以可以把 Exclude<keyof T, K>
看作是一个反选,选出了 T 中那些不包含在 K 中的属性,然后在用 Pick ,就实现了 Omit 。
2 实战用法
interface Person {
name: string
age: number
id: string
work: string
address: string
girlFriend: number
}
// 没女朋友的人
type PersonNoGirlFriend =Omit<Person, 'girlFriend'>
//相当于:
type PersonNoGirlFriend =Pick<Person, 'name' | 'age' | 'id' | 'work' | 'address'>
源码和代码中我们可以看到一个新的类型 —— Pick。它的意思是“挑取”。
即从类型 Type 中,挑选一组属性组成一个新的类型返回。这组属性由 Keys 限定, Keys 是字符串或者字符串并集。源码如下:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
K extends keyof
,表示 K 需要是 keyof T
的子集。返回的类型的键需要满足[P in K]
,值类型满足T[P]
。
以上是关于深入typescript之‘可选的’和‘必要的’的主要内容,如果未能解决你的问题,请参考以下文章