「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
其实大多数 TypeScript 开发者,对 TypeScript 的利用,还停留在初级水平。
不信?来试试 TypeScript 每周挑战吧!
第 2 期挑战的题目是:如何在 Pick<A|B> 后仍然保持 Union Type 中的字段关系。先来回顾一下题目吧~
题目回顾
对于类型定义:
type Data = {
type: 'a',
value: {
a: string
},
a1: string,
a2: string,
// ...
} | {
type: 'b',
value: {
b: string
},
b1: string,
b2: string,
// ...
} | {
type: 'c',
value: {
c: string
},
c1: string,
c2: string,
// ...
}; // 可能还有更多项
type PickData = Pick<Data, 'type' | 'value'>;
经过 TypeScript 的转换处理,PickData 变成了:(不期望)
type PickData = {
type: "a" | "b" | "c";
value: {
a: string;
} | {
b: string;
} | {
c: string;
};
}
而实际 期望的 PickData 结果应该是:
type PickData = {
type: 'a',
value: {
a: string
}
} | {
type: 'b',
value: {
b: string
}
// ...
} | {
type: 'c',
value: {
c: string
}
};
请你重新定义 问题 中的 PickData,使其在 Pick 了 type 和 value 字段后仍能保持原始 Data 中的互斥匹配关系。
请尝试在代码量和类型冗余最小的情况下实现。
答案公布
type PickUnion<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
type PickData = PickUnion<Data, 'type' | 'value'>;
神奇吗,加上 T extends any ? XXX : never 的判断,竟然就可以了!
这是为什么呢?
原理解析
Pick<T, K> 实际上是 TypeScript 的 Mapped Type 特性:
Pick<T, U extends keyof T> = {
[K in U]: T[K]
}
Mapped Type 字面意思就是映射类型,映射就是从一个类型映射到另一个类型。
所以对于题目而言,Pick<Data, 'type'|'value'> 的映射结果就是:
{
type: Data['type'],
value: Data['value']
}
可以看到,映射过后的类型,字段间并没有关联,所以 Union Type 中的关系也就不存在了。
因此,TypeScript 提供了一种叫做 条件类型重组 (Distributive Conditional Types)的机制来解决类似的问题。其写法就是:
type XXX<T> = T extends any ? AAA : BBB;
如果泛型参数 T 是一个条件类型(例如 Union Type),则 TypeScript 在推导类型时会进行重组操作,举个例子,假设你给 T 传递的是 A | B:
// 重组前
type XXX<A | B> = (A | B) extends any ? AAA : BBB;
// 重组后 ↓↓↓
type XXX<A | B> = (A extends any ? AAA : BBB) |
(B extends any ? AAA : BBB);
所以当我们实现为 PickUnion 的时候:
type PickUnion<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
我们传入的 T: Data 由于是一个条件类型 A | B | C,所以它被重组成了:
(A extends any ? Pick<A, K> : never) |
(B extends any ? Pick<B, K> : never) |
(C extends any ? Pick<C, K> : never)
如此,就保留了条件类型中的字段关系。
了解更多条件类型重组的信息,参见 TypeScript 官方手册文档: Distributive Conditional Types
往期挑战回顾
如果你对使用 TypeScript 进行全栈开发感兴趣,欢迎关注目前世界上唯一支持 TypeScript 复杂类型运行时自动检测和二进制序列化的TypeScript 开源 RPC 框架 —— TSRPC。
GitHub:github.com/k8w/tsrpc
中文文档:tsrpc.cn
视频教程:www.bilibili.com/video/BV1hM…