在TypeScript中实现可修改泛型类型的建造者模式的最简洁方式
在TypeScript中实现可修改泛型类型的建造者模式的最简洁方式
看起来你已经摸对了核心方向——用泛型交集(&)逐步扩展最终对象的类型!当前的编译错误主要是因为TypeScript不知道你正在给this.widget添加新属性,所以会阻止你访问原本不属于T的字段。下面是最简洁且贴合TypeScript类型系统的修正方案:
核心思路
我们需要在调用建造者方法时,安全地告知TypeScript当前对象已可容纳新增属性,同时通过泛型交集更新Builder的类型,让最终build()返回的对象自动包含所有建造者方法添加的属性,还能完整保留初始传入的自定义属性。
修正后的完整代码
type BaseWidget = { id: string; } class WidgetBuilder<T extends BaseWidget> { private widget: T; constructor(widget: T) { this.widget = widget; } withColors() { // 断言widget为扩展后的类型,安全添加colors属性 (this.widget as T & { colors: string }).colors = "red"; // 更新Builder的泛型类型,标记已添加colors属性 return this as unknown as WidgetBuilder<T & { colors: string }>; } withSize() { (this.widget as T & { size: number }).size = 9; return this as unknown as WidgetBuilder<T & { size: number }>; } build(): T { return this.widget; } } function main() { const widget1 = new WidgetBuilder({ id: "1" }) .withColors() .withSize() .build(); widget1.size; // ✅ 类型自动推导为number,无错误 widget1.colors; // ✅ 类型自动推导为string,无错误 widget1.id; // ✅ 正常访问基础属性 const widget2 = new WidgetBuilder({ id: "2", foo: "bar" }) .withColors() .build(); widget2.colors; // ✅ 正确识别新增属性 widget2.foo; // ✅ 完整保留初始自定义属性 widget2.id; // ✅ 正常访问基础属性 }
方案细节说明
- 类型断言的合理性:我们把
this.widget断言为T & { colors: string },是因为明确知道接下来会给它添加colors属性,这个断言完全符合代码逻辑,不存在类型安全风险。 - 泛型的逐步扩展:通过
return this as unknown as WidgetBuilder<T & { colors: string }>,我们告诉TypeScript:调用withColors()后,当前Builder处理的对象类型已经包含了colors属性。 - 初始属性的保留:泛型
T会继承初始传入的对象类型(比如widget2的foo属性),所以最终build()返回的对象会自动整合「初始属性 + 建造者方法新增属性」。
可选的不可变写法(无直接属性修改)
如果偏好不可变对象的设计风格,也可以通过浅拷贝扩展属性,类型推导逻辑和上面完全一致:
withColors() { this.widget = { ...this.widget, colors: "red" } as T & { colors: string }; return this as unknown as WidgetBuilder<T & { colors: string }>; }
总结
这个方案完美实现了你想要的效果:从基础泛型类型出发,随着建造者方法的调用自动扩展最终对象的类型,同时代码简洁、完全符合TypeScript类型系统的规范。
内容来源于stack exchange
相关产品推荐
相关产品推荐

