#typescript
Вопрос:
Я столкнулся с проблемой при вводе объекта с помощью TypeScript
Я объявил тип, который используется некоторыми файлами приложения:
type Category = "cat1"|"cat2"|"cat3"|"cat4"
Я использую Category
для ввода объекта, и все работает нормально, используя:
const obj: {
[key in Category]: string
} = {---}
Но теперь я хотел бы добавить 2 ключа, которые не Category
являются значениями для объекта.
Я думал, что это будет легко, введя объект следующим образом:
const obj: {
customKey1: string
customKey2: string
[key in Category]: string
} = {---}
Но вместо ожидаемой работы TS отправляет 3 ошибки: TS2464, TS1170 и TS2693
A computed property name must be of type 'string', 'number', 'symbol' or 'any'. ts(2464)
'Category' only refers to a type, but is using as a value here. ts(2693)
A computed property name in a type template must refer to an expression whose type is a literal type or a 'unique symbol' type. ts(1170)
ОК… Почему?
Поскольку Category
он используется для файлов, я не хочу редактировать его, добавляя эти пользовательские ключи, которые нужны только здесь.
Я решил эту проблему, поэтому, если кто-то еще сталкивается с этой проблемой, я даю решение ниже, но кто-нибудь знает, почему моя первая идея не может быть применена?
Я не понимаю, почему запись [key in Category]: string
работает, если она одна, но выдает ошибки, если добавлен другой ключ.
Ответ №1:
{[K in XXX]: YYY}
Синтаксис представляет собой сопоставленный тип, специальный тип объекта, который перебирает объединяющие элементы ключевого выражения типа XXX
и использует параметр типа K
для каждого из них внутри YYY
выражения типа. (Обратите внимание, K
представляет тип, а не значение ключа свойства, поэтому по соглашению мы используем там символы верхнего регистра). Вы можете использовать K
внутри YYY
выражения, чтобы каждый ключ K
в XXX
мог иметь другой тип значения, например {[K in "a" | "b"]: K}
, эквивалентен {a: "a", b: "b"}
. Синтаксис сопоставленного типа не является обобщаемым или расширяемым; вы не можете поместить туда другие свойства, например {[K in XXX]: YYY; somethingElse: string}
, и вы не можете поместить их в interface
объявления или сделать с ними что-либо еще. Это синтаксическая ошибка. В некотором смысле фигурные скобки { ... }
являются частью синтаксиса для сопоставленных типов, и хотя они выглядят как фигурные скобки в других типах объектов, они не действуют как они.
Важно не путать этот синтаксис с похожим {[k: XXX]: YYY}
синтаксисом для индексных подписей. Сопоставленные типы используют in
ключевое слово, в то время как подписи индекса этого не делают, и требуют фиктивного идентификатора имени ключа ( k
в приведенном здесь примере). Фиктивное имя ключа существует только внутри ключа и не может использоваться в YYY
выражении, поэтому оно не позволяет вам назначать разные значения для разных ключей в XXX
; подписи индекса никоим образом не перебирают ключи внутри XXX
. В качестве подписей индексные подписи могут использоваться в любом типе объекта наряду с другими свойствами, например {[k: XXX]: YYY; somethingElse: string}
, при условии, что другие свойства не конфликтуют с индексной подписью. Они могут быть включены в interface
объявления. Фигурные скобки не являются частью синтаксиса для подписей индексов.
Поэтому ваш вопрос: почему вы не можете добавить другие свойства к сопоставленному типу? почему фигурные скобки в сопоставленных типах работают не так, как в других типах объектов?
В microsoft / TypeScript # 13573 возникла проблема с этим точным вопросом. Однако, похоже, окончательного ответа нет. Это похоже на непредвиденный вариант использования. Некоторое время было возможно, что запрос на извлечение в microsoft / TypeScript # 26797 будет объединен с языком в рамках усилий по унификации сопоставленных типов и индексных подписей, но этого никогда не происходило. Соответствующий комментарий в microsoft / TypeScript # 45089 от члена команды TS объясняет, что на данный момент маловероятно, что здесь что-то изменится, поскольку возникают вопросы о том, как работать с возможно конфликтующими типами и обобщениями:
Смешивание отображаемых типов и объявлений свойств приводит к странным последствиям, которые элегантно решаются с помощью пересечений
Я не буду подробно описывать их здесь; вы можете посмотреть связанную проблему для получения дополнительной информации.
Итак, это ответ на вопрос «почему это так», насколько это возможно. Итак, что вы можете сделать вместо этого? В приведенном выше комментарии упоминаются пересечения; вы можете объединить свойства двух типов объектов вместе с помощью пересечений, поэтому {a: string} amp; {b: string}
, по сути, это то же {a: string; b: string}
самое, что и . Итак, один из способов написать свой тип объекта — это:
type MyObjType =
{ [K in Category]: string } amp;
{ customKey1: string, customKey2: string };
Вы не можете напрямую использовать пересечения в качестве типов интерфейса, но вам разрешено расширять интерфейс другими именованными типами со статически известными ключами. Ваш {[K in Category]: string}
имеет статически известные ключи, потому Category
что не является универсальным, но это не именованный тип. Вы можете либо присвоить ему имя самостоятельно, а затем расширить его:
type CategoryProps = { [K in Category]: string };
interface MyObjType extends CategoryProps {
customKey1: string
customKey2: string
}
Или вы можете использовать Record<K, V>
тип утилиты и расширить его, не объявляя новое имя:
interface MyObjType extends Record<Category, string> {
customKey1: string
customKey2: string
}
Наконец, вы всегда можете пойти в другом направлении и использовать сопоставленный тип для всех ваших ключей вместо того, чтобы пытаться добавлять их по отдельности:
type MyObjType =
{ [K in Category | "customKey1" | "customKey2"]: string };
Комментарии:
1. Большое вам спасибо за этот полный ответ. Я думаю, ссылки, которыми вы поделились, помогут мне лучше понять TS. Читая сказанное, я предполагаю, что я действительно не различал сопоставленные типы и индексные подписи, я собираюсь подробно разобраться с этими понятиями. И спасибо за решения, первое — это то, которое я использовал в своем проекте. Я думаю, что я буду использовать второй с
Record
типом утилиты, более гибкий, чем 3-й (некоторые пользовательские ключи могут иметь разные типы).2. Это окончательное руководство по предоставлению ответа SO. Объяснения ПОЧЕМУ и ссылки с github немного смягчили разочарование. Хотя я надеюсь, что команда TS поймет преимущества этого нового синтаксиса. Если пересечение работает, то, скорее всего, этот новый синтаксис может быть закодирован как трюк интерпретатора. Еще раз спасибо!
Ответ №2:
Для тех, кому не нужны объяснения, а только доступное решение, вот мое:
type Keys = { [key in Category]: string }
interface Obj extends Keys {
customKey1: string
customKey2: string
}
const obj: Obj = {---}
Все работает нормально.