TypeScript:如何为类添加对应字符串联合类型的属性以实现DRY?
解决方案:实现DRY原则的TypeScript类属性与类别管理
首先,我们要解决两个核心需求:
- 避免重复维护
Category类型和categories数组 - 让类自动拥有
Category中所有类别的属性,无需手动声明每个属性
步骤1:用单一来源管理类别(消除重复)
我们可以用const对象或枚举来作为类别的单一真实来源,这样类型和运行时数组都从这里派生,完全符合DRY原则。
方案A:使用const对象(推荐,更灵活)
// 单一来源:定义所有类别,运行时可用 const CATEGORY_MAP = { people: 'people', projects: 'projects', topics: 'topics', tools: 'tools' } as const; // 从对象键自动生成联合类型,无需手动写`'people' | 'projects'...` export type Category = keyof typeof CATEGORY_MAP; // 自动生成categories数组,完全依赖CATEGORY_MAP,不用重复写 const categories: Category[] = Object.keys(CATEGORY_MAP) as Category[];
方案B:使用枚举
如果更喜欢枚举的语法,也可以这样:
enum Category { People = 'people', Projects = 'projects', Topics = 'topics', Tools = 'tools' } // 从枚举值自动生成数组 const categories = Object.values(Category);
步骤2:实现类的自动属性赋值
接下来,我们需要让类实现IPostEntryIndex接口,同时无需手动声明每个类别属性。这里利用TypeScript的索引签名和构造函数赋值来实现:
// 假设你的Entry接口定义如下 interface Entry { id: string; // 示例构造函数 constructor() { this.id = crypto.randomUUID(); } } // 索引类型保持不变(或用Record<Category, Entry>简化) type IPostEntryIndex = Record<Category, Entry>; class PostEntryIndex implements IPostEntryIndex { // 索引签名允许我们在构造函数中动态赋值属性 [key: string]: Entry; constructor() { // 遍历categories数组,为每个类别创建Entry实例 categories.forEach(cat => { this[cat] = new Entry(); }); } }
额外优化:严格类型检查(可选)
上面的实现中,TypeScript允许通过任意字符串键访问类的属性(因为索引签名是string)。如果想要严格限制只能访问Category中的键,可以通过类型断言或工具类型来约束:
// 定义严格类型,移除通用字符串索引,只保留Category的键 type StrictPostEntryIndex = Omit<PostEntryIndex, string> & IPostEntryIndex; // 创建实例时断言为严格类型 const index = new PostEntryIndex() as StrictPostEntryIndex; // 正确访问 console.log(index.people.id); // 错误:TypeScript会报错,因为"nonexistent"不在Category中 // console.log(index.nonexistent);
为什么这符合DRY原则?
- 新增类别时,只需要修改
CATEGORY_MAP(或枚举),Category类型、categories数组、类的属性会自动更新 - 无需手动在类中声明每个类别属性,构造函数会自动完成赋值
内容的提问来源于stack exchange,提问作者shaunc
相关产品推荐
相关产品推荐

