多个类型之间的两两转换
enum TopicGql {
TodoApproval = "TodoApproval",
DoneApproval = "DoneApproval",
InitiatedApproval = "InitiatedApproval",
UnreadNotice = "UnreadNotice",
ReadNotice = "ReadNotice",
Shared = "Shared",
}
enum TopicB2e {
TodoApproval = 1,
DoneApproval = 2,
InitiatedApproval = 3,
UnreadNotice = 17,
ReadNotice = 18,
Shared = 33,
}
...更多类型
常见的几种类型转换
基于 Map
export const topicGqlToB2eMap = {
[TopicGql.TodoApproval]: [TopicB2e.TodoApproval],
[TopicGql.DoneApproval]: [TopicB2e.DoneApproval],
[TopicGql.InitiatedApproval]: [TopicB2e.InitiatedApproval],
[TopicGql.UnreadNotice]: [TopicB2e.UnreadNotice],
[TopicGql.ReadNotice]: [TopicB2e.ReadNotice],
[TopicGql.Shared]: [TopicB2e.Shared],
}
基于函数封装: if-else 或 switch-case
export const topicGqlToB2e = (value: TopicGql) => {
if (value === TopicGql.TodoApproval) return TopicB2e.TodoApproval;
if (value === TopicGql.DoneApproval) return TopicB2e.DoneApproval;
if (value === TopicGql.InitiatedApproval) return TopicB2e.InitiatedApproval;
if (value === TopicGql.UnreadNotice) return TopicB2e.UnreadNotice;
if (value === TopicGql.ReadNotice) return TopicB2e.ReadNotice;
if (value === TopicGql.Shared) return TopicB2e.Shared;
}
当我们开始使用元组
const topicTupleList: [TopicGql, TopicB2e, 更多类型][] = [
[TopicGql.DoneApproval, TopicB2e.DoneApproval, 更多类型],
[TopicGql.InitiatedApproval, TopicB2e.InitiatedApproval, 更多类型],
[TopicGql.ReadNotice, TopicB2e.InitiatedApproval, 更多类型],
[TopicGql.Shared, TopicB2e.Shared, 更多类型],
[TopicGql.TodoApproval, TopicB2e.TodoApproval, 更多类型],
[TopicGql.UnreadNotice, TopicB2e.UnreadNotice, 更多类型],
];
数组 vs 元组
官方文档: www.typescriptlang.org/docs/handbo…
-
数组 Array 是 js 中的一种数据类型.
-
元组 Tuple 本质上也是数组. 但在 ts 语境下, 我们需要与数组进行区分
从定义的元组中实现类型转换
export const topicGqlToB2e = (value: TopicGql) =>
topicTupleList.find((tuple) => tuple[0] === value)?.[1];
优势:
- 对于两种以上枚举 (如国际化语言 zh-cn / zh_CN / zh) 之前互相转换
缺点:
-
仍然要分别实现转换方法
-
实现中的元组下标容易写错
高阶函数封装 + 类型定义
将二维数组想象成数据库表, 我们可以先对表头进行类型定义.
enum TopicStyleIndex {
Gql = 0,
B2e = 1,
}
type TopicTuple = [TopicGql, TopicB2e];
创建高阶函数, 利用参数推导类型
const createTopicGen =
<From extends TopicStyleIndex, To extends TopicStyleIndex>(from: From, to: To ) => {
return (value: TopicTuple[From]): TopicTuple[To] | undefined =>
topicTupleList.find((tuple) => tuple[from] === value)?.[to];
};
export const topicGqlToB2e = createTopicGen(
TopicStyleIndex.Gql,
TopicStyleIndex.B2e
);
export const topicB2eToGql = createTopicGen(
TopicStyleIndex.B2e,
TopicStyleIndex.Gql
);
性能优化
转换查询仅对二维数组做一层(行)遍历, 二层(列)使用下标查询, 一般不会有性能问题.
- 除非你的枚举可取值非常多, 从而外层(行)数组非常长
- 除非你的转换操作非常频繁
并不包括枚举类型非常多, 因为内层(列)使用下标查询.
基于索引的 map 对性能来说是必要的 (牺牲空间换时间), 可以在转换方法调用时实时创建 map
将二维数组想象成数据库表的话, 可以把创建 map 想象成建立索引
const createTopicGen = <
From extends TopicStyleIndex,
To extends TopicStyleIndex
>(
from: From,
to: To
) => {
const map = new Map(); // 可以使用 js 对象代替, 但 js 对象的 key 只能是字符串
const nil = {}; // 使用唯一值表示 undefined, 防止与枚举值中的 undefined 冲突
return (value: TopicTuple[From]): TopicTuple[To] | undefined => {
const ret = map.get(value);
if (!ret) {
const _ret = topicTupleList.find((tuple) => tuple[from] === value)?.[to];
map.set(value, _ret);
return _ret;
}
if (ret === nil) {
return undefined;
}
return ret;
};
};
与 Map 配合使用
没有银弹, 元组也不可能适用于所有情况
一些场景下可能还是需要 函数封装的 if-else 或 switch-case
- 来源类型的多个值要转成目标类型的一个值, 或来源类型的一个值可以转成目标类型的多个值
也会有一些场景需要元组和 Map 来配合使用.
-
如果只存在单向转换(只有一个索引字段), 建议还是定义 map, 例如通过 key 获取一个对象, 获取一个 icon 啥的.
-
如果存在多个索引字段, 可以将元组与 map 配合使用, 元组仅作为索引字段之间的转换, 指定一个 key 用来在 map 中查找其他值.
Map 推荐使用场景
例如:
id | name | avatar | |
---|---|---|---|
xx | xxx | xxx@xxx.xxx | xxxx |
如果只是通过 id 转换到其他字段, 可以定义 map
{
[id]: { name, email, avatar }
}
而不建议 (非要用也行, 不就是不好看) 元组, 因为 name, avatar 不可被索引.
元组 + Map 使用场景
如前文所示, 如果也需要 email 来查找 name 和 avatar,
这个时候, 可以先创建 [id, email][] 的元组定义
就可以通过 email 先找到 id, 再通过 Map 中的索引 id 来找到其它字段.