Typescript 强制泛型类型安全

问题描述 投票:0回答:1

我知道由于 Javascript 的动态特性,Typescript 是结构化类型,因此像泛型这样的功能与其他语言的名义类型系统不同。鉴于此,我们如何使用泛型(特别是数组)强制类型安全?假设我有这些类/类型:

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')
    }
}

我使用了类,但这对于类型来说也是一样的。

这些表达式是可以理解的,因为我们明确地声明了类型(如果我们忽略两者具有相同的无类型结构的事实,则类似于

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

我知道在这种情况下,像数组这样的泛型是通过使用而不是声明来强制执行的,例如,

arrY.forEach(val => val.fn([0])
将会中断。我完全了解结构化类型系统的限制,所以我不是问为什么,或者为什么不应该,我是在寻求一种好方法来强制执行这种限制。我不介意任何解决方法。我想要实现的本质上是用某种方式来传达这一点:我们可以将
Y
用作
X
,但决不能将
X
用作
Y
。我还知道有更多方法可以对两种“类型”之间的关联进行建模,因此我不需要可以涵盖所有边缘情况的通用解决方案。

我尝试重新命名通用品牌,如下所示:

type YEnv = string & {__unused: 'Y' }
class Y /* extends break */ extends X {
    fn (env: YEnv) {...}
}

现在由于

YEnv
number|string
不兼容,继承被破坏。此类 API 的使用者需要将
Y
显式转换为
X
才能在
Array<X>
中使用。在任何标准类型系统上我们都不需要这样做。显式转换它们很好,但可能不是很直观。

typescript generics types
1个回答
0
投票

一般来说,TypeScript 的 结构类型系统 意味着类型

X
可以分配给类型
Y
,具体取决于类型的 形状,而不一定取决于它们的 声明。如果在编写
class Y extends X {}
时没有将冲突的内容添加到
Y
中,那么您通常会遇到这样一种情况:
X
可以分配给
Y
,尽管它是它的超类。

考虑到 TypeScript 并不完美,可分配性问题只会变得更加复杂和难以思考,因此一些允许的分配“应该”被拒绝,除了必须接受等效分配以允许现有分配类型层次结构。有关更多信息,请参阅 microsoft/TypeScript#9825。 TypeScript 中的方法的参数类型是双变的,不安全,这意味着您的 Y

 确实不应该被允许作为 
X
 的子类。但确实如此,我不会担心你为什么会这样,除了建议未来的读者要小心这样的事情。

无论如何,当您将

X

 分配给 
Y
 当您不希望它成为并且您可以控制这两种类型时,标准方法是向 
Y
 添加一些内容以使其成为不相容。最简单的方法是添加 
X
 中不存在的成员。房产就可以:

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

Y

 包含 
y
 中不存在的数字 
X
 属性。由于我使用了 
declare 修饰符
,这实际上根本不会改变发出的 JavaScript。它只是说,根据 TypeScript,它就在那里。当然,它在运行时不存在,因此您编写的任何代码都应该远离它,而不是尝试读取或写入它。您可以将其设置为 
private 以阻止外部访问,或者您可以将其设置为 undefined
 以更好地模拟正在发生的情况,或者您可以将其设置为 
Y
以便不会出现运行时问题。人们可能想要通过各种方式进行操作,具体取决于他们的用例。
重要的是:当你不小心有兼容的类型时,至少修改其中一个不兼容的类型。
Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.