Могу ли я заставить компилятор TypeScript использовать номинальную типизацию? - PullRequest
1 голос
/ 21 марта 2019

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!

Однако, как поставщик реализации someFunction, я хотел бы действительно запретить передачу структурно похожих объектов, но я действительно хочу разрешить только реальные экземпляры MyClassTest или его подтипы. Я хочу применить «номинальную типизацию» по крайней мере для некоторых моих собственных объявлений функций.

Возможно ли это?

Справочная информация. Рассмотрим случай, когда объекты, передаваемые в API, должны быть этого типа, например потому что они были созданы фабрикой, которая устанавливает некоторое внутреннее состояние для этого объекта, и объект фактически имеет закрытый интерфейс, который someFunction ожидает, чтобы работать должным образом. Однако я не хочу раскрывать этот закрытый интерфейс (например, в файле определения машинописного текста), но я хочу, чтобы это было ошибкой компилятора, если кто-то передает ложную реализацию. Конкретный пример: я хотел бы, чтобы компилятор машинописи жаловался в этом случае, даже если я предоставлю все члены следующим образом:

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

1 Ответ

3 голосов
/ 21 марта 2019

Нет флага компилятора, чтобы заставить компилятор вести себя номинально, действительно, в принципе не может быть такого флага, который нарушил бы многие сценарии 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 определен в lib.d.ts как интерфейс и const, так что добавление его в приват Поле почти невозможно (без изменений lib.d.ts), и даже если бы мы могли, оно потенциально может нарушить существующий код и определения.

...