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

时间:2019-03-21 13:08:49

标签: typescript duck-typing

TypeScript使用structural subtyping,因此实际上可行:

// 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!

但是,作为someFunction的实现提供者,我确实希望禁止传递结构相似的对象,但是我实际上只希望允许MyClassTest或其子类型的真实实例。我想至少对我自己的某些函数声明强制执行“标称类型”。

这可能吗?

背景:请考虑以下情况:传递给API的对象必须属于该类型,例如因为它们是由工厂生产的,该工厂在此对象上设置了一些内部状态,并且该对象实际上具有一个专用接口,someFunction希望该接口可以正常工作。但是我不想透露该私有接口(例如在打字稿定义文件中),但是如果有人传入假的实现,我希望它是编译器错误。具体的例子:在这种情况下,即使我提供所有成员,我都希望打字稿编译器抱怨:

//OK for typescript, breaks at runtime
window.document.body.appendChild({nodeName:"div", namespaceURI:null, ...document.body}) 

1 个答案:

答案 0 :(得分:3)

没有编译器标志可以使编译器以名义上的方式运行,实际上原则上不能存在这样的标志,因为它会破坏很多JavaScript场景。

通常使用几种技术来模拟某种名义上的类型。品牌类型通常用于基元(从here中选择一个作为示例),对于类,一种常见的方法是添加一个私有字段(私有字段在结构上不会与任何东西匹配,除非有确切的私有字段定义) 。

使用最后一种方法,您的初始样本可以写为:

// 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我认为我们无法做任何事情,因为Nodelib.d.ts中定义为接口和{{1} },因此几乎不可能(不对const进行任何更改)用私有字段进行扩展,即使我们能够这样做,也有可能破坏许多现有的代码和定义。