如何在TypeScript中高效创建类的不可变扩展?
更高效的ImmutablePerson实现方案
你可以通过几种方式消除代码冗余,同时满足继承、不可变性和反向类型转换的需求:
方案1:参数属性+Readonly接口约束
利用TypeScript的参数属性减少重复属性声明,同时通过implements Readonly<Person>确保类结构符合只读要求:
class Person { id: number; name: string; } class ImmutablePerson extends Person implements Readonly<Person> { constructor(public readonly id: number, public readonly name: string) { super(); } }
这里参数属性直接在构造函数里完成了属性的声明、初始化和readonly修饰,不需要在类体里重复写id和name字段。implements Readonly<Person>则从类型层面保证了类的属性和只读版Person完全匹配。
方案2:通用不可变子类生成工具
如果需要复用这种“把普通类转成不可变子类”的逻辑,可以写一个通用工具函数,避免重复代码:
type ImmutableConstructor<T> = new (...args: any[]) => Readonly<T>; function createImmutableClass<T extends new (...args: any[]) => any>(BaseClass: T): ImmutableConstructor<InstanceType<T>> { return class extends BaseClass { constructor(...args: ConstructorParameters<T>) { super(...args); Object.freeze(this); // 运行时冻结实例,防止修改 } // 类型层面将所有属性标记为只读 readonly [K in keyof InstanceType<T>]: InstanceType<T>[K]; } as unknown as ImmutableConstructor<InstanceType<T>>; } // 使用示例 const ImmutablePerson = createImmutableClass(Person); // 反向转换直接赋值即可,子类实例天然兼容父类类型 const mutablePerson: Person = new ImmutablePerson(1, "Alice");
这个工具会动态生成继承自基类的不可变子类,类型层面通过映射类型把所有属性设为只读,运行时用Object.freeze确保实例无法被修改。
方案3:复用父类构造逻辑
如果父类本身有构造函数,可以直接复用它的逻辑,进一步简化:
class Person { constructor(public id: number, public name: string) {} } class ImmutablePerson extends Person implements Readonly<Person> { constructor(id: number, name: string) { super(id, name); Object.freeze(this); } // 类型层面标记属性为只读(可选,因为implements已经约束了类型) readonly id: number; readonly name: string; } // 反向转换示例 const person: Person = new ImmutablePerson(2, "Bob");
关于反向类型转换
ImmutablePerson是Person的子类,TypeScript类型系统天然允许将子类实例赋值给父类类型变量,不需要额外的转换操作——这是面向对象继承的基本特性。
内容的提问来源于stack exchange,提问作者Jon Vance
相关产品推荐
相关产品推荐

