如何用TypeScript泛型编写可扩展、可维护且DRY的树形库类型?
解决方案:可扩展、可维护的LinkMap泛型类型
TL;DR
我需要在库中定义如下结构的类型,要求具备可扩展性、可维护性且符合DRY原则:
const myProjectLinks: LinkMap<Region, Platform> = { global: { // 区域 ios: 'a', // 平台 android: 'b', huawei: 'c', }, us: { ios: 'd', }, };
问题背景
我正准备将组件提取到库中,其中包含复杂类型。最终对象结构如上,但当前的LinkMap类型与项目强绑定,直接硬编码了区域和平台列表:
export enum LinkPlatform { Ios = 'ios', Android = 'android', Huawei = 'huawei', } export enum LinkRegion { Global = 'global', Us = 'us', } export type LinkMap = { [key in LinkRegion]?: { [key in LinkPlatform]?: string; }; };
存在以下痛点:
- 代码重复:枚举键名与字符串值内容重复,仅大小写不同
- 无法安全重命名:IDE无法自动同步修改字符串值的所有引用
- 扩展性差:增删区域或平台必须修改库代码,无法由项目自主控制
已尝试的方案及问题
我试过泛型、字面量类型等方案,但都存在缺陷:
泛型+字面量类型
存在代码重复,部分内容无法自动重命名:
// 库侧代码 type LinkMap<Env extends string, Store extends string> = { [Key in Env]: { [Key in Store]?: string; }; }; // 项目侧代码 // 只能重命名枚举键,无法自动同步修改值,也没有定义提示 const enum LinkRegion { Global = 'global', Us = 'us', } // 无法自动重构字符串值的所有引用 type LinkPlatform = 'ios' | 'android' | 'huawei'; // 类型安全但上述问题未解决 const myProjectLinks: LinkMap<LinkRegion, LinkPlatform> = { global: { ios: '', android: '', huawei: '', }, us: { ios: '', }, };
基于对象类型的方案
存在大量无用的any类型,类型提示缺失:
// 库侧代码 type LinkMap<Region extends { [Key in keyof Region]: any }, Platform extends string> = { [Key in keyof Region]: { [Key in Region]?: string; }; }; // 项目侧代码 type LinkRegion = { global: any; // 无用的any,无实际作用也无类型提示 us: any; }; // ...
最终解决方案
核心思路是让库只提供通用的泛型类型定义,将区域、平台的具体定义完全交予项目侧,同时保证类型安全与IDE可操作性。
库侧通用类型定义
// 库中仅定义通用泛型,不绑定具体业务值 export type LinkMap< Region extends string, Platform extends string > = Partial<Record<Region, Partial<Record<Platform, string>>>>; // 可选:提供辅助函数,简化对象创建并自动推导类型 export function createLinkMap< R extends string, P extends string >(links: LinkMap<R, P>): LinkMap<R, P> { return links; }
项目侧使用方式
项目侧可通过两种方式定义区域和平台,均支持IDE自动重命名:
方式1:字符串枚举(推荐)
枚举的键与值绑定,IDE支持一键重命名所有引用:
import { LinkMap, createLinkMap } from 'your-library'; // 项目自定义区域枚举 export enum LinkRegion { Global = 'global', Us = 'us', // 新增区域直接在此添加,无需修改库 } // 项目自定义平台枚举 export enum LinkPlatform { Ios = 'ios', Android = 'android', Huawei = 'huawei', // 新增平台同理 } // 直接指定类型 const myProjectLinks: LinkMap<LinkRegion, LinkPlatform> = { [LinkRegion.Global]: { [LinkPlatform.Ios]: 'a', [LinkPlatform.Android]: 'b', [LinkPlatform.Huawei]: 'c', }, [LinkRegion.Us]: { [LinkPlatform.Ios]: 'd', }, }; // 或用辅助函数自动推导类型(可选) const myProjectLinks = createLinkMap({ [LinkRegion.Global]: { [LinkPlatform.Ios]: 'a', [LinkPlatform.Android]: 'b', [LinkPlatform.Huawei]: 'c', }, [LinkRegion.Us]: { [LinkPlatform.Ios]: 'd', }, });
方式2:字面量类型联合
若不想使用枚举,字面量类型联合同样支持IDE重命名:
import { LinkMap } from 'your-library'; // 项目自定义区域字面量类型 export type LinkRegion = 'global' | 'us'; // 项目自定义平台字面量类型 export type LinkPlatform = 'ios' | 'android' | 'huawei'; // 使用类型约束 const myProjectLinks: LinkMap<LinkRegion, LinkPlatform> = { global: { ios: 'a', android: 'b', huawei: 'c', }, us: { ios: 'd', }, };
方案优势
- 无代码重复:枚举或字面量直接定义所需字符串,无需重复书写
- IDE友好:支持一键重命名所有相关引用,避免手动修改遗漏
- 完全可扩展:项目侧可自由增删区域/平台,无需改动库代码
- 符合DRY原则:库只提供通用逻辑,业务定义由项目自主控制
内容的提问来源于stack exchange,提问作者Welnyr
相关产品推荐
相关产品推荐

