TS 自定义泛型工具例子-修改使定义的部分属性必须存在
工作中常常用 API 的入参是非必填的,而实例的属性因为有默认值而一定存在的情况,举个例子:
type TestOptions = {
num?: number
str?: string
hookFn?: () => string
}
const defaultOptions = {
num: 1,
str: 'test'
}
Class Test {
options: TestOptions
constructor (options: TestOptions) {
this.options = Object.assign({}, defaultOptions, options)
}
excute () {
this.options.num // error: 类型“boolean | undefined”的参数不能赋给类型“boolean”的参数。
}
}
上述代码中,我们先忽略 options
有开发者主动传入 { num: undefined }
的情况。
实际上我们期望的是被 defaultOptions
辅助设置过后, this.options.num
一定会存在,而不用每次都得加以判断。至于钩子函数 this.options.hookFn
我们的确希望它只能被上层开发者传入而存在。
为此我们需要改一下 this.options
的类型,思路是这样的:
Pick<TestOptions, 'num' | 'str'>
从类型中选取num
和str
两个属性,假设新类型取名叫type A = { num?: number; str?: string }
- 加上
Required<...>
使得 A 的属性全都要求必须存在,假设新类型取名叫type B = { num: string; str: string }
Omit<TestOptions, 'num' | 'str'>
使从类型中排除num
和str
,假设新类型取名叫type C = { hookFn?: () => string }
- 将 B 和 C 两类型连结起来得到新类型,新类型里
num
和str
要求必须存在,而其他的属性依照原类型定义不做改变。假设新类型取名叫type D = { num: string; str: string; hookFn?: () => string }
class Test {
options: Required<Pick<TestOptions, 'num' | 'str'>> & Omit<TestOptions, 'num' | 'str'>
}
这么写过于啰嗦,所以用一个自定义的泛型工具替代:
export type PickForRequired<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>
class Test {
options: PickForRequired<TestOptions, 'num' | 'str'>
excute () {
// this.options.
// num
// str
// hookFn?
}
}
最后完善下 this.options
赋值的逻辑,去避开 options
中开发者主动传入某属性为 undefined 的情况,借用 lodash 函数去合并对象:
this.options = _.assignWith({}, defaultOptions, options, function (objectVal, sourceVal) {
return _.isUndefined(sourceVal) ? objectVal : sourceVal
})