Тип объединения как ключ в интерфейсе?

#typescript

#typescript

Вопрос:

Возможно ли использовать тип объединения в качестве ключа в интерфейсе? Например, я хочу сделать что-то вроде этого:

 interface IMargin {
  [key in 'foo' | 'bar']: boolean;
}
  

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

Имя вычисляемого свойства в интерфейсе должно ссылаться на выражение, тип которого является литеральным типом или типом «уникального символа».ts(1169)

Есть ли какой-либо способ обойти это?

Вариант использования заключается в преобразовании массива значений в интерфейс:

 const possibleTypes = ['foo', 'bar'];
interface Types {
    foo?: boolean;
    bar?: boolean;
}
  

Комментарии:

1. Может быть keyof Types ?

Ответ №1:

Вы можете использовать тип объекта вместо интерфейса, которые в основном взаимозаменяемы:

 type IMargin = {
    [key in 'foo' | 'bar']: boolean;
}
  

Комментарии:

1. Это полезно, спасибо — использование типа вместо интерфейса помогает мне частично достичь этого. Однако, как вы можете видеть в моем комментарии к ответу Тициана, я все еще застрял, генерируя тип объединения из массива.

2. Я получаю A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1169)

Ответ №2:

Это не обязательно ответ, но я думаю, что это может заинтересовать других.

Тип объединения можно использовать в качестве ключа во вложенном свойстве интерфейса.

 export type Language = 'EN' | 'DE' | 'IT';

export interface Document {
  generic: string;
  languages: {
    [key in Language]: string[];
  }
}
  

Комментарии:

1. Это обрабатывается как использование type ключевого слова. Если бы вы использовали type вместо interface , вам было бы разрешено делать это и там.

2. но что в случае, если я не хочу указывать все языки? При этом, прямо сейчас, требуются все языки. Как гарантировать, что ключ просто принадлежит указанным ключам, не требуя их всех?

3. Привет @noisy, я знаю, что это старый комментарий, но удалось ли вам найти обходной путь, чтобы использовать только один из языков в качестве одного ключа вместо нескольких возможных ключей?

Ответ №3:

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

 const possibleTypes = (<T extends string[]>(...o: T) => o)('foo', 'bar');
type Types = Record<typeof possibleTypes[number], boolean>
  

Сопоставленный тип Record должен работать здесь

Комментарии:

1. Привет, спасибо за твой ответ. Это работает нормально, пока вы вводите ('foo', 'bar') напрямую. Однако, это не работает, когда вы передаете (...['foo', 'bar']) . Есть ли какой-либо способ исправить это? Примером использования здесь является генерация типа или интерфейса из массива.

2. @ElliotBonneville ты имеешь в виду для IIFE в possibleTypes ? Идея в том, что массив должен иметь литеральный тип, который либо вводится как кортеж ['foo', 'bar'] , либо как массив Array<'foo' | 'bar'> . Если вы правильно ввели этот массив, вы можете просто использовать его без IIFE. В противном случае вам либо нужно ввести assert для чего-либо, относящегося к одному из указанных выше типов. Если это просто синтаксическое предпочтение использовать массив, вы можете использовать const possibleTypes = (<T extends string>(o: T[]) => o)(['foo', 'bar']); но мне больше нравится оригинальная версия 🙂

3. @ElliotBonneville (...['foo', 'bar']) не работает, это ограничение ts. Литерал массива будет выведен на string[] перед операцией распространения, и, таким образом, типы литералов будут потеряны

4. А, понятно. Это помогает больше, и мы добиваемся своего! Я все еще застреваю, хотя использую это в своем реальном варианте использования. У меня такое чувство, что тип строкового литерала где-то теряется при переводе. Вот чего я пытаюсь достичь: stackblitz.com/edit/typescript-dftj9c

5. @ElliotBonneville это лошадь другого цвета .. к сожалению, вы не можете создавать типы строковых литералов из конкатенаций. Ваши типы все равно теряются, потому что параметры являются типами as string[] , которые могут быть решены с помощью дженериков. Но вам все еще не удалось сгенерировать prefix1_a в качестве строкового литерала тип в системе типов в настоящее время.

Ответ №4:

преобразовать возможные типы в enum преобразовать интерфейс в type, поскольку тип параметра подписи индекса не может быть литеральным типом или универсальным типом. Рассмотрите возможность использования сопоставленного типа объекта вместо

 enum possibleTypes { 'foo', 'bar' };
type Types {
    [key in keyof typeof possibleTypes]: boolean
}
  

Ответ №5:

 const possibleTypes = ['foo', 'bar'] as const;
type Types = Record<typeof possibleTypes[number], boolean>
  

Говоря, as const мы объявляем, что массив является неизменяемым, а не просто Array<string> .
possibleTypes[number] необходим для фильтрации методов массива, например length , из типа.