我知道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) {...}
}

YEnvnumber|string是不兼容的,继承被 destruct 了.这样的API的消费者将需要显式地将Y转换为X,以便在Array<X>中使用.在任何命名类型的系统中,我们都不需要这样做.显式地转换它们是可以的,但可能不是很直观.

推荐答案

一般而言,TypeScrip structural type system意味着类型X可根据类型中的shapes而不一定是它们的declarations来分配给类型Y.如果编写class Y extends X {}而不将冲突的内容添加到Y中,则通常会出现这样的情况:尽管XY的超类,但X仍可赋值给Y.

给定TypeScript isn't perfectly sound,可赋值性的问题只会变得更加复杂和难以思考,所以一些允许的赋值"应该"被拒绝,除了必须接受等效的赋值以允许现有类型层次 struct .microsoft/TypeScript#9825了解更多信息TypeScript中的方法是bivariant in their parameter types不安全的,这意味着你的Y真的不应该被允许作为X的子类.但事实确实如此,我不会担心你为什么会这样做,除了建议future 的读者小心这类事情.

无论如何,当你不希望它被分配给Y时,标准的方法是给Y添加一些东西,使它不兼容.最简单的方法是添加一个X中不存在的成员. 物业将:

class Y extends X {
    declare y: number; // <-- add this
    override fn(env: string[]) { }
}

在这里,TypeScript认为Y包含了一个X中不存在的数字y属性. 因为我使用了the declare modifier,所以实际上并没有改变发出的JavaScript.它只是说,根据TypeScript,它在那里. 当然,它在运行时是isn't,所以你写的任何代码都应该远离它,而不是试图读或写它.你可以把它设置为private以阻止外部访问,或者你可以把它设置为undefined以更好地模拟正在发生的事情,或者你可以将它添加到Y,这样就不会出现运行时的问题.有各种各样的方式,人们可能想要继续下go ,这取决于他们的用例.

重要的是:当您有意外兼容的类型时,请至少将其中一个修改为不兼容.

Playground link to code

Typescript相关问答推荐

有没有办法适当缩小类型?

如何推断类方法未知返回类型并在属性中使用它

类型安全JSON解析

HttpClient中的文本响应类型选项使用什么编码?''

为什么tsx不判断名称中有"—"的属性?'

数组文字中对象的从键开始枚举

Angular 信号:当输入信号改变值时,S触发取数的正确方式是什么?

部分类型化非 struct 化对象参数

如何以Angular 形式显示验证错误消息

如何解决类型T不能用于索引类型{ A:typeof A; B:typeof B;. }'

是否可以从函数类型中创建简化的类型,go 掉参数中任何看起来像回调的内容,而保留其余的内容?

路由链接始终返回到

在HighChats列时间序列图中,十字准线未按预期工作

使用KeyOf和Never的循环引用

正确使用相交类型的打字集

TaskListProps[]类型不可分配给TaskListProps类型

如何在Deno中获取Base64图像的宽/高?

我可以使用JsDocs来澄清Typescript实用程序类型吗?

如何从联合类型推断函数参数?

如何让 Promise 将它调用的重载函数的类型转发给它自己的调用者?