我知道TypeScript是 struct 化类型,由于Javascript的动态特性,因此泛型等特性与其他语言的名义类型系统不一样.鉴于此,我们如何使用泛型(特别是数组)来强制类型安全?假设我有这些类/类型:
class X {
fn(env: (number | string)[]) {
if (typeof env[0] === 'string') {
console.log('print string and number')
}
console.log(env[0] === 0)
}
}
class Y extends X {
override fn(env: string[]) {
console.log(env[0] === '0')
}
}
我使用了类,但类型也是一样的.
这些表达式是可以理解的,因为我们显式地声明了类型(如果忽略两者都具有相同的无类型 struct ,则类似于as
):
const x: X = new Y()
const y: Y = new X()
但例如,这些表达式也是有效的
const arrX: X[] = [y] // works, as intended Y extends X
const arrY: Y[] = [x] // works, but shouldn't, or at least emit a warning
我知道像Array这样的泛型在这种情况下是通过使用而不是声明来实现的,例如,arrY.forEach(val => val.fn([0])
将中断.我非常清楚 struct 化类型系统的局限性,所以我不是在问为什么或者为什么不应该,我是在寻求一种好的方法来实施这种限制.我不介意任何变通办法.我想要实现的基本上是某种方式来传达这一点:我们可以用Y
作为X
,但绝不能用X
作为Y
.我也意识到有更多的方法来建模2个"类型"之间的关联,所以我不需要一个可以涵盖所有边缘情况的通用解决方案.
我试着重新命名仿制药,看起来像这样:
type YEnv = string & {__unused: 'Y' }
class Y /* extends break */ extends X {
fn (env: YEnv) {...}
}
YEnv
和number|string
是不兼容的,继承被 destruct 了.这样的API的消费者将需要显式地将Y
转换为X
,以便在Array<X>
中使用.在任何命名类型的系统中,我们都不需要这样做.显式地转换它们是可以的,但可能不是很直观.