Почему это дополнительное свойство разрешено в моем объекте Typescript? - PullRequest
4 голосов
/ 13 марта 2019

Недавно мы начали использовать машинописный текст для наших проектов веб-платформ.

Предполагалось, что одним из значительных преимуществ будет мощная система ввода, которая позволяет проверять правильность всех типов во время компиляции (при условии, что мыприложить усилия для того, чтобы смоделировать и объявить наши типы должным образом).

В настоящее время я, кажется, нашел пределы того, что может достичь система типов, но это кажется непоследовательным, и я также могу просто использовать неправильносинтаксис.

Я пытаюсь смоделировать типы объектов, которые наше приложение будет получать из серверной части, и использовать систему типов, чтобы компилятор проверял всюду в приложении:

  1. структура, т. Е. Только существующие (перечисляемые) свойства разрешены компилятором TS для объектов типа
  2. проверки типов свойств, т.е. тип каждого свойства известен компилятору TS

Вот минимизированная версия моего подхода (или прямая ссылка на игровую площадку TS )

interface DataObject<T extends string> {
    fields: {
        [key in T]: any   // Restrict property keys to finite set of strings
    }
}

// Enumerate type's DB field names, shall be used as constants everywhere
// Advantage: Bad DB names because of legacy project can thus be hidden in our app :))
namespace Vehicle {
    export enum Fields {
        Model = "S_MODEL",
        Size = "SIZE2"
    }
}

// CORRECT ERROR: Property "SIZE2" is missing
interface Vehicle extends DataObject<Vehicle.Fields> {
    fields: {
        [Vehicle.Fields.Model]: string,
    }
}

// CORRECT ERROR: Property "extra" is not assignable
interface Vehicle2 extends DataObject<Vehicle.Fields> {
    fields: {
        extra: string
    }
}

// NO ERROR: Property extra is now accepted!
interface Vehicle3 extends DataObject<Vehicle.Fields> {
    fields: {
        [Vehicle.Fields.Model]: string,
        [Vehicle.Fields.Size]: number,
        extra: string  // Should be disallowed!
    }
}

Почему третье объявление интерфейса не выдает ошибку, когда компилятор, похоже, вполне может запретить неверное имя свойства во втором случае?

Ответы [ 2 ]

2 голосов
/ 13 марта 2019

Если вы представляете, что fields - это такой интерфейс:

interface Fields {
    Model: string;
    Size: number;
}

(Это сделано анонимно, но соответствует этому интерфейсу из-за вашего [key in Vehicle.Fields]: any)

Тогдаэто терпит неудачу, потому что не не соответствует этому интерфейсу - у него нет свойства Model или Size:

fields: {
    extra: string
}

Однако, это проходит:

fields: {
    Model: string;
    Size: number;
    extra: string
}

Поскольку у анонимного интерфейса есть расширение вашего Fields интерфейса.Это выглядело бы примерно так:

interface ExtendedFields extends Fields {
    extra: string;
}

Все это делается анонимно с помощью компилятора TypeScript, но вы можете добавить свойства к интерфейсу и по-прежнему соответствовать интерфейсу, как расширенный класс все ещеэкземпляр базового класса

1 голос
/ 13 марта 2019

Это ожидаемое поведение.Базовый интерфейс определяет только минимальные требования для field, в машинописном тексте нет требования для точного соответствия между полем реализующего класса и полем интерфейса.Причина, по которой вы получаете сообщение об ошибке Vehicle2, заключается не в наличии extra, а в том, что другие поля отсутствуют.(Нижняя ошибка - Property 'S_MODEL' is missing in type '{ extra: string; }'.)

. Вы можете сделать некоторую хитрость типов, чтобы получить ошибку, если эти дополнительные свойства присутствуют с использованием условных типов:

interface DataObject<T extends string, TImplementation extends { fields: any }> {
    fields: Exclude<keyof TImplementation["fields"], T> extends never ? {
        [key in T]: any   // Restrict property keys to finite set of strings
    }: "Extra fields detected in fields implementation:" & Exclude<keyof TImplementation["fields"], T>
}

// Enumerate type's DB field names, shall be used as constants everywhere
// Advantage: Bad DB names because of legacy project can thus be hidden in our app :))
namespace Vehicle {
    export enum Fields {
        Model = "S_MODEL",
        Size = "SIZE2"
    }
}

// Type '{ extra: string; [Vehicle.Fields.Model]: string; [Vehicle.Fields.Size]: number; }' is not assignable to type '"Extra fields detected in fields implementation:" & "extra"'.
interface Vehicle3 extends DataObject<Vehicle.Fields, Vehicle3> {
    fields: {
        [Vehicle.Fields.Model]: string,
        [Vehicle.Fields.Size]: number,
        extra: string // 
    }
}
...