如何为基于枚举映射的TypeScript结果声明类型?
我有两个TypeScript枚举和一个映射常量:
enum FirstEnum { firstEnumKey1 = 'firstEnumKey1', firstEnumKey2 = 'firstEnumKey2', firstEnumKey3 = 'firstEnumKey3', firstEnumKey4 = 'firstEnumKey4', } enum SecondEnum { secondEnumKey1 = 'secondEnumKey1', secondEnumKey2 = 'secondEnumKey2', secondEnumKey3 = 'secondEnumKey3', } // 映射FirstEnum到SecondEnum数组 const map: {[i in FirstEnum]: SecondEnum[] } = { 'firstEnumKey1': [SecondEnum.secondEnumKey1], 'firstEnumKey2': [], 'firstEnumKey3': [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3], 'firstEnumKey4': [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3], };
我需要基于这个map定义一组数据,每个SecondEnum成员对应一个对象,对象的键是map中值数组包含该SecondEnum成员的FirstEnum成员,结构示例如下:
const getSecondEnumValuesBasedOnMap = { [SecondEnum.secondEnumKey1]: { [FirstEnum.firstEnumKey1]: 'some value', }, [SecondEnum.secondEnumKey2]: { [FirstEnum.firstEnumKey3]: 'some value', [FirstEnum.firstEnumKey4]: 'some value', }, [SecondEnum.secondEnumKey3]: { [FirstEnum.firstEnumKey3]: 'some value', [FirstEnum.firstEnumKey4]: 'some value', }, }
当map发生变更时(比如给FirstEnum.firstEnumKey2的数组添加SecondEnum.secondEnumKey2),这个数据结构需要同步更新。我希望给结果声明一个TypeScript类型,让map变更时,编译器能提示我更新结果。
要实现这个需求,我们可以通过TypeScript的条件类型和映射类型推导基于map的强约束类型,具体步骤如下:
1. 让TypeScript精准推导map的类型
首先不要手动指定map的类型,而是用as const让TypeScript推导最窄的字面量类型,这样类型能完全反映map的实际内容:
const map = { [FirstEnum.firstEnumKey1]: [SecondEnum.secondEnumKey1], [FirstEnum.firstEnumKey2]: [], [FirstEnum.firstEnumKey3]: [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3], [FirstEnum.firstEnumKey4]: [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3], } as const;
2. 定义辅助类型推导映射关系
我们需要两个辅助类型来建立SecondEnum到FirstEnum的反向映射:
// 提取map的具体类型 type MapType = typeof map; // 推导每个SecondEnum成员对应的FirstEnum成员集合 type SecondToFirstMap = { [S in SecondEnum]: { // 遍历所有FirstEnum成员,判断当前S是否在其对应的SecondEnum数组中 [F in keyof MapType]: S extends MapType[F][number] ? F : never }[keyof MapType] // 过滤掉never类型,只保留符合条件的FirstEnum成员 };
3. 定义最终结果类型
基于SecondToFirstMap生成最终的结果类型,约束每个SecondEnum对应的对象结构:
// 这里的string可以替换成你实际需要的值类型 type ResultType = { [S in SecondEnum]: { [F in SecondToFirstMap[S]]: string; } };
4. 用ResultType约束数据对象
现在用ResultType来声明你的数据对象,编译器会自动校验结构是否符合map的映射关系:
const getSecondEnumValuesBasedOnMap: ResultType = { [SecondEnum.secondEnumKey1]: { [FirstEnum.firstEnumKey1]: 'some value', }, [SecondEnum.secondEnumKey2]: { [FirstEnum.firstEnumKey3]: 'some value', [FirstEnum.firstEnumKey4]: 'some value', }, [SecondEnum.secondEnumKey3]: { [FirstEnum.firstEnumKey3]: 'some value', [FirstEnum.firstEnumKey4]: 'some value', }, };
验证效果
当你修改map的内容时,比如给FirstEnum.firstEnumKey2添加SecondEnum.secondEnumKey2:
const map = { [FirstEnum.firstEnumKey1]: [SecondEnum.secondEnumKey1], [FirstEnum.firstEnumKey2]: [SecondEnum.secondEnumKey2], // 新增映射 [FirstEnum.firstEnumKey3]: [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3], [FirstEnum.firstEnumKey4]: [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3], } as const;
此时TypeScript编译器会立即提示getSecondEnumValuesBasedOnMap中的secondEnumKey2对象缺少firstEnumKey2键,强制你同步更新数据结构,实现了映射变更后的自动校验。
内容的提问来源于stack exchange,提问作者mhd sah

