背景
利用泛型进行属性关联
大家在业务中一定很熟悉这样的场景,针对某个action,传递一个指定类型的payload。
有了ts之后,我们会期望用泛型将action和payload的对应关系约束起来。
例如下面这段demo
type Payloads = {
people: { name: string, age: number };
machine: { id: string, price: number };
}
type Keys = keyof Payloads;
type Foo<T extends Keys, P> = {
key: T;
payload: P;
info: string;
};
type AllFoos = { [K in Keys]: Foo<K, Payloads[K]> };
type AnyFoo = AllFoos[Keys];
这里我首先定义了action和payload结构关联的map,然后创建类型ALLFoos,利用泛型对象将action的key和对应的payload结构关联起来。最后输出的AnyFoo类型就是所有有效Foo类型的联合类型。
现在我就可以利用ts的类型检查,确保key和payload的关系约束了。
const makeFoo = (key: Keys): AnyFoo => {
if (key === 'people') {
return {
key,
payload: { name: 'xinyuehtx', age: 18 },
info: '帅'
}
} else {
return {
key,
payload: { id: '终结者', price: 998 },
info: '莫得感情'
}
}
}
如果我们将“终结者”的属性名称改成“name”,ts就会帮我们报错
属性关联类型解构
那么现在问题来了,假如我要新增一个对外开放的API,要从Foo这个类型中去掉info属性,但是又不能改变Foo现有的结构。这个需求常见于内部方法对外开放时,去除一些隐私信息。毕竟面对第三方,˙终结者不希望别人知道他是一个莫得感情的机器,我也不希望别人发现我很帅。
第一时间我想到的方案如下,建立一个Bar类型,定义如Foo,但是去掉了info属性。
然后从foo对象中选取特定属性进行赋值。
type Bar<T extends Keys, P> = {
key: T;
payload: P;
};
type AllBars = { [K in Keys]: Bar<K, Payloads[K]> };
type AnyBar = AllBars[Keys];
const makeBarFromFoo = (foo: AnyFoo): AnyBar => ({
key: foo.key,
payload: foo.payload,
})
但是这里ts报错了。为什么呢?在这里,ts能够识别foo.key是一个'people'|'machine'
的联合类型,foo.payload也是对应payload结构的联合类型,但是他们之间的关联丢失了。
ts不能消除people对应{id,price}的可能性。
解决方案
- 通过as强转:
这个是最偷懒的方案,直接跳过检查,通过人工约束。可行,但是非到万不得已不要用 - 在实现代码中判断payload属性
const makeBarFromFoo = (foo: AnyFoo): AnyBar => {
if (foo.key === 'people' && foo.payload.name && foo.payload.age) {
return {
key: foo.key,
payload: foo.payload,
}
}
//...剩余代码
}
这种方法对于小范围情况还可以,但是对于大量类型处理就会有点力不从心了
- 终极方案rest解构
这里我们的本质需求是从一个对象中获取关联的两个属性,且确保关联不丢失。这个需求换种思路,就是从指定对象中去除一些属性,返回剩余的。这个正好就是rest解构的场景。
所以就是如下的代码
const makeBarFromFoo = (foo: AnyFoo): AnyBar => {
const { info, ...rest } = foo;
return rest
}
ts识别也正常,没有报错
参考链接: