#typescript
Вопрос:
Допустим, у нас есть интерфейс Animal
interface Animal {
amountOfLegs: number;
}
А затем два разных класса реализуют этот интерфейс, Dog
и Snake
:
class Dog implements Animal {
constructor(public amountOfLegs: number) {
}
}
class Snake implements Animal {
amountOfLegs: number;
constructor() {
this.amountOfLegs = 0;
}
}
Затем мы хотим создать функцию, специфичную для Змеи:
function functionSpecificForSnake(snake: Snake): any {
//Do something with the snake object...
}
Но когда функция вызывается с помощью объекта Dog, TS не жалуется на неправильные типы:
functionSpecificForSnake(new Dog(4)); // No compiler error, but possible runtime error?
Итак, мой вопрос: могу ли я предотвратить такое поведение? Я знаю, что это называется совместимостью типов, но я не вижу способа предотвратить это. Использование "strictFunctionTypes": true
опции в моем tsconfig.json, похоже, ничего не дает.
Комментарии:
1. Я рекомендую добавить ссылку на игровую площадку для машинописи к вашему вопросу. typescriptlang.org/play/index.html
2. @sunknudsen готово
Ответ №1:
Ваши классы Dog
и Snake
являются структурно эквивалентными типами, поэтому они могут быть назначены друг другу в том, что касается машинописи. Поведение, которое вы хотите, соответствует системе номинальных типов, которой нет в Typescript, но вы можете эмулировать ее, изменив типы, чтобы они были структурно отличными.
Для этого вы можете добавить свойство с именем что-то вроде __brand
, которое не противоречит какому-либо реальному свойству , которое может иметь тип, и свойство может иметь отдельный тип для каждого класса; это может быть просто сам класс или имя класса в виде строкового литерала. Чтобы избежать каких-либо затрат во время выполнения, вы можете объявить свойство без его инициализации, поэтому свойство на самом деле не существует, но Typescript думает, что оно существует. Чтобы отключить ошибку для неинициализированного свойства, вы можете сделать это свойство необязательным (или использовать @ts-ignore
).
class Dog implements Animal {
private readonly __brand?: Dog;
constructor(public amountOfLegs: number) {}
}
class Snake implements Animal {
private readonly __brand?: Snake;
amountOfLegs: number;
constructor() {
this.amountOfLegs = 0;
}
}
Затем, если вы попытаетесь использовать a Dog
там, где Snake
ожидается a, вы получите ошибку типа, потому __brand
что свойство имеет неправильный тип.
Ответ №2:
Я считаю, что TypeScript проверяет типы свойств, поэтому ваша проблема заключается в том, что они оба Dog
Snake
имеют одинаковые свойства и типы… так что они идентичны.
interface Animal {
amountOfLegs: number;
}
class Dog implements Animal {
constructor(public amountOfLegs: number) {
}
}
class Snake implements Animal {
public amountOfLegs: number;
public venomous: boolean;
constructor() {
this.amountOfLegs = 0;
this.venomous = true;
}
}
function functionSpecificForSnake(snake: Snake): void {
//Do something with the snake object...
}
functionSpecificForSnake(new Dog(4));
Ответ №3:
Как бы то ни было, нет, ты не можешь. В ссылке, на которую вы ссылались: «Чтобы проверить, может ли y быть присвоен x, компилятор проверяет каждое свойство x, чтобы найти соответствующее совместимое свойство в y». Таким образом, компилятор игнорирует бит «реализует интерфейсы» классов и просто смотрит на форму каждого класса.
Змеи и собаки имеют одинаковую форму, поэтому их можно отнести друг к другу.
Вы могли бы попробовать добавить дискриминатор в каждый класс, что-то вроде:
interface Animal {
disc: string
amountOfLegs: number;
}
class Dog implements Animal {
disc = 'Dog' as const
constructor(public amountOfLegs: number) {
}
}
class Snake implements Animal {
disc = 'Snake' as const
amountOfLegs: number;
constructor() {
this.amountOfLegs = 0;
}
}
function functionSpecificForSnake(snake: Snake): any {
//Do something with the snake object...
}
functionSpecificForSnake(new Dog(4))
Теперь это приводит к ошибке
Аргумент типа «Собака» не может быть присвоен параметру типа «Змея». Типы свойств «диск» несовместимы. Тип «Собака» не может быть присвоен типу»Змея». (2345)
Ответ №4:
Уже есть несколько хороших объяснений, но я также рекомендую посмотреть здесь. Это помогло мне глубже понять проблему.