Почему интерфейс не работает при определении типа, который использует перечисление в качестве ключа свойства?

#typescript

Вопрос:

Если у меня есть перечисление и я хочу использовать значения перечисления в качестве ключа в некоторых объектах, я могу объявить типы следующим образом:

 enum Foo {
    X, Y
}

let map: FooMapType = {
    [Foo.X]: "abc",
    [Foo.Y]: "def"
}

type FooMapType = {
    [key in Foo]: string
}
 

Это работает совершенно нормально. Однако, если я попытаюсь сделать то же самое с помощью интерфейса:

 interface FooMapInterface {
    [key in Foo]: string
}

 

Я получаю эти ошибки:

 TS1169: A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
TS2464: A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
TS2304: Cannot find name 'key'.
 

Почему в этом случае существует разница между интерфейсом и типом? Каковы ограничения на интерфейс, если таковые имеются, которые привели к этому дизайнерскому решению?

Ответ №1:

Вам не разрешается использовать сопоставленные ключи типа (формы [P in K]: T , где K тип похож на ключ) в качестве ключей an interface . Прямо сейчас вы получаете несколько сбивающих с толку сообщений об ошибках, потому что компилятор неверно истолковывает то, что вы делаете, как использование объявления вычисляемого свойства (формы [k]: U , в которой k используется значение, подобное ключу, а не тип). Существует открытая проблема в microsoft/TypeScript#18299 с просьбой исправить эти сообщения об ошибках.

Я не знаю, есть ли какая-либо каноническая документация, в которой точно говорится, почему вы не можете использовать сопоставленные типы здесь. В дополнение к обычным свойствам, ключи которых являются статически известными строковыми или числовыми литеральными типами, интерфейсам разрешается иметь подписи вызовов, подписи конструкций, подписи методов и подписи индексов, все из которых также разрешены внутри class типов экземпляров. Сопоставленные типы не входят в список допустимых вещей.


Если вы хотите использовать interface сопоставленный тип, вы можете сделать это, расширив сопоставленный тип в качестве нового интерфейса. Вам разрешено расширять именованный тип с помощью статически известных ключей. И поскольку ключи FooMapType статически известны (в отличие от некоторого неопределенного универсального типа), вы можете написать это:

 interface FooMapInterface extends FooMapType { } // okay
 

И проследите, чтобы это сработало:

 declare const fooMapInterface: FooMapInterface;
fooMapInterface[Foo.X].toUpperCase(); // okay
 

Таким образом, вы можете получить такое поведение, вы просто не можете написать его напрямую.

Ссылка на игровую площадку для кода