我可以强制TypeScript编译器使用名义类型吗?

4

TypeScript使用结构子类型,因此实际上是有可能的:

// there is a class
class MyClassTest {
    foo():void{}
    bar():void{}
}

// and a function which accepts instances of that class as a parameter
function someFunction(f:MyClassTest):void{
    if (f){
        if (!(f instanceof MyClassTest)) {
            console.log("why")
        } 
    }
}

// but it's valid to pass objects, which aren't in fact instances
// they just need to "look" the same, be "structural subtypes"
someFunction({foo(){}, bar(){}})  //valid!

然而,作为一名实施提供者,我想禁止传递结构类似的对象,但我只想允许MyClassTest或其子类型的真实实例。我至少想强制执行“命名类型”在我的某些函数声明中。这是否可能?背景:考虑一个情况,API所需的对象必须是该类型,例如因为它们由设置了该对象的某些内部状态的工厂生产,并且该对象实际上具有someFunction希望存在以便正常工作的私有接口。然而,我不想透露该私有接口(例如在typescript定义文件中),但如果有人传入假实现,则希望它成为编译器错误。具体示例:在此情况下,即使我提供了所有成员,我也希望typescript编译器抱怨。
//OK for typescript, breaks at runtime
window.document.body.appendChild({nodeName:"div", namespaceURI:null, ...document.body}) 

现在这个有一个名字:这些类型被称为“品牌类型” - 甚至有一个特殊的unique关键字,可以帮助创建品牌类型 - Sebastian
1个回答

7

没有编译器标志可以使编译器以名义方式运行,实际上不能有这样的标志,因为那将破坏许多JavaScript场景。

一般使用几种技术来模拟某种名义类型。品牌类型通常用于原始数据类型(从这里选一个作为示例),对于类,一种常见的方法是添加一个私有字段(私有字段不会结构上匹配除了该私有字段定义之外的任何内容)。

使用最后一种方法,您的初始示例可以编写为:

// there is a class
class MyClassTest {
    private _useNominal: undefined; // private field to ensure nothing else is structually compatible.
    foo():void{}
    bar():void{}
}

// and a function which accepts instances of that class as a parameter
function someFunction(f:MyClassTest):void{
    if (f){
        if (!(f instanceof MyClassTest)) {
            console.log("why")
        } 
    }
}


someFunction({foo(){}, bar(){}, _useNominal: undefined})  //err

针对您具体的情况,使用append,我认为我们无能为力,因为Node被定义为一个接口和一个const,所以增加私有字段几乎是不可能的(除非更改lib.d.ts),即使我们可以这样做,也可能会破坏大量现有的代码和定义。

1
谢谢!实际上我并不是在寻找全局编译器标志。很明显这样的标志可能会破坏很多代码。我希望能够添加一些修改器到参数类型中(比如 someFunction(f:MyClassTest!)),以便仅对该方法进行更改。"private" hack 很酷 - 但我想它在类型定义文件中不起作用,因为私有定义没有太大意义。 - Sebastian
1
@Sebastian,你可以在声明中使用私有字段,这没有任何问题,因为TS需要知道私有字段以防止意外覆盖。但在这种情况下,没有一种方法可以增强现有的定义。 - Titian Cernicova-Dragomir
1
实际上,像 strictNullChecks => nominalTypeChecks 这样的全局(可选)标志是可行的,我想 - 我不认为它会比 strictNullCheck 破坏更多的代码。无论如何,interfaces 仍然是鸭子类型。我不认为这些假的“类”被如此频繁地使用。 - Sebastian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接