工作中常常用 API 的入参是非必填的,而实例的属性因为有默认值而一定存在的情况,举个例子:
type TestOptions = {
num?: number
str?: str
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 }
的情况。实际上我们期望的结果是 this.options.num
一定会存在,不用每次都得加以判断,而 this.options.hookFn
倒是不一定存在。
也就是说我们需要改一下 this.options
的类型,思路是这样的:
class Test {
options: Required<Pick<TestOptions, 'num' | 'str'>> & Omit<TestOptions, 'num' | 'str'>
}
解释:
- 从定义中选取
num
和str
两个属性Pick<TestOptions, 'num' | 'str'>
- 加 Required 使得 Pick 的新定义的属性全都要求必须存在
Omit<TestOptions, 'num' | 'str'>
使从定义中排除num
和str
得到其他属性- 将 2 和 3 两个对象连结起来得到新对象,新对象里
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
})