如何让TypeScript禁止泛型参数类型的不安全转换?
TypeScript泛型容器的不安全转换问题及解决方案
能否让编译器报告此类不安全转换错误?
可以。通过开启TypeScript的严格类型检查模式即可实现:
- 在
tsconfig.json中设置"strict": true(推荐,开启所有严格检查规则),或者单独开启"strictFunctionTypes": true。
开启后,编译器会直接抛出编译错误,阻止TContainer<Bar>赋值给TContainer<Foo>这类不安全操作。
为什么默认允许这种不安全转换?
TypeScript默认对泛型参数采用双向协变的类型兼容性规则,这是为了兼容JavaScript的灵活编程风格,降低前端开发者的迁移门槛。而C#、Java等语言的泛型是不变的,即Container<Sub>和Container<Super>之间完全不兼容,从根源上禁止了这类操作。
双向协变虽然带来了灵活性,但当泛型类型包含可写成员(比如示例中的public value: T)时,就会出现运行时崩溃问题——你可以通过父类型容器修改内部值,破坏子类型容器的类型约束。
如何让TContainer这类泛型类型保证安全?
除了开启严格模式外,还有两种针对性方案:
1. 给泛型类型添加逆变位置约束
如果不想全局开启严格模式,可以给TContainer添加一个仅接受T类型参数的方法,利用逆变位置的参数会约束泛型兼容性的特性,让编译器自动阻止不安全赋值:
interface Foo { sayHello(): void; } class Bar implements Foo { sayHello(): void { console.log("Bar Hello!"); } sayGoodbye(): void { console.log("Bar Goodbye!"); } } class Baz implements Foo { sayHello(): void { console.log("Baz Hello!"); } } class TContainer<T extends Foo> { public value: T; constructor(value: T) { this.value = value; } // 新增方法,让T成为不变类型 setValue(value: T): void { this.value = value; } } function run() { const barContainer: TContainer<Bar> = new TContainer(new Bar()); // 此时编译器会报错:类型"TContainer<Bar>"不能赋值给类型"TContainer<Foo>" const fooContainer: TContainer<Foo> = barContainer; }
2. 使用只读泛型容器
如果容器不需要修改内部的value,可以将其设置为只读,这样即使发生了不安全赋值,也无法修改内部值,从根本上避免运行时崩溃:
class ReadonlyTContainer<T extends Foo> { public readonly value: T; constructor(value: T) { this.value = value; } } function run() { const barContainer: ReadonlyTContainer<Bar> = new ReadonlyTContainer(new Bar()); // 即使允许赋值,也无法修改value const fooContainer: ReadonlyTContainer<Foo> = barContainer; // 编译器会报错:无法分配到 "value" ,因为它是只读属性 fooContainer.value = new Baz(); }
内容的提问来源于stack exchange,提问作者Tacodiva
相关产品推荐
相关产品推荐

