如何在TypeScript中实现互不兼容的不透明类型约束?
此问题与其他类型定义问题无关。
请观察以下代码:
interface Machine<OpaqueType> { get(): OpaqueType, update(t: OpaqueType); } const f = <U, V>(uMachine: Machine<U>, vMachine: Machine<V>) => { const u = uMachine.get(); vMachine.update(u); }
这段代码最后一行无法编译,报错信息:
Argument of type 'U' is not assignable to parameter of type 'V'. 'V' could be instantiated with an arbitrary type which could be unrelated to 'U'.
这正是预期效果:两台机器各自处理自身类型的数据,未经刻意协调无法互相操作。
但泛型<U, V>的写法略显冗余,因为函数并不关心具体类型。希望写成如下形式,且让这段代码无法编译(因为两个unknown对应的实际类型不一定兼容):
const f = (uMachine: Machine<unknown>, vMachine: Machine<unknown>) => { const u = uMachine.get(); vMachine.update(u); }
补充说明:第二段代码本就不应通过编译,这是错误写法,希望编译器能捕获该错误。
请问在TypeScript中能否实现这种需求?
可以实现,核心是通过泛型关联两个Machine实例的实际类型,避免用unknown抹除类型信息,同时让编译器能检测类型兼容性。
方法一:用单一泛型约束两个Machine的类型一致性
让函数声明一个泛型参数,要求传入的两个Machine必须属于同一类型,这样既不用显式写<U, V>,又能保证类型不匹配时报错:
interface Machine<OpaqueType> { get(): OpaqueType; update(t: OpaqueType): void; } // 泛型T约束两个Machine的类型必须一致 const f = <T>(uMachine: Machine<T>, vMachine: Machine<T>) => { const u = uMachine.get(); vMachine.update(u); // 类型匹配,编译通过 } // 测试:传入不同类型的Machine会触发报错 const numMachine: Machine<number> = { get: () => 1, update: () => {} }; const strMachine: Machine<string> = { get: () => "a", update: () => {} }; f(numMachine, strMachine); // 报错:类型不兼容,符合预期
方法二:用辅助类型隐式检查类型兼容性
如果需要保留“参数看起来是Machine<unknown>”的形式,可以通过辅助类型在编译期检查两个Machine的实际类型是否兼容:
interface Machine<OpaqueType> { get(): OpaqueType; update(t: OpaqueType): void; } // 辅助类型:若U不能赋值给V,则返回never触发报错 type AssertAssignable<U, V> = U extends V ? unknown : never; // 泛型自动推导,无需显式声明U、V const f = <U, V>( uMachine: Machine<U>, vMachine: Machine<V> & AssertAssignable<U, V> ) => { const u = uMachine.get(); vMachine.update(u); } // 测试:不同类型的Machine传入会报错 f(numMachine, strMachine); // 编译报错,符合需求
为什么直接用Machine<unknown>不行?
unknown是顶级类型,任何类型都能赋值给它,且unknown值可以赋值给unknown。当参数声明为Machine<unknown>时,u的类型是unknown,vMachine.update接受的也是unknown,编译器会认为类型兼容,因此不会报错。要实现需求,必须通过泛型追踪两个Machine的实际类型,而非用unknown抹除类型信息。
内容的提问来源于stack exchange,提问作者Michael Lorton

