Как в TypeScript типизировать объект с известными и неизвестными ключами?
Я ищу способ создать типы TypeScript для следующего объекта, который имеет два известных ключа и один неизвестный ключ с известным типом:
interface ComboObject {
known: boolean;
field: number;
[U: string]: string;
}
const comboObject: ComboObject = {
known: true,
field: 123,
unknownName: 'value'
}
Этот код не работает, потому что TypeScript требует, чтобы все свойства соответствовали типу, заданному для индексной сигнатуры. Однако я не хочу использовать индексные сигнатуры, я хочу типизировать одно поле, тип которого я знаю, но имя которого не знаю.
Единственное решение, которое у меня есть на данный момент, это использовать индексные сигнатуры и создать объединение типов всех возможных типов:
interface ComboObject {
[U: string]: boolean | number | string;
}
Но у этого есть много недостатков, включая возможность указать неправильные типы для известных полей, а также разрешение произвольного количества неизвестных ключей.
Существует ли лучший подход? Могут ли чем-то помочь условные типы в TypeScript 2.8?
3 ответ(ов)
Похоже, что более простой способ сделать это — использовать тип пересечения:
type ComboObject = {
known: boolean;
field: number;
} & {
[key: string]: string | number | boolean;
};
Этот код говорит TypeScript наследовать свойства слева, но при этом не выдает ошибок, когда вы добавляете дополнительные параметры с неизвестными типами.
Существует и более подробное и сложное решение, которое может предоставить дополнительные ограничения, явно исключая известные типы:
type ComboObject = {
known: boolean;
field: number;
} & Record<Exclude<string, "known" | "field">, string | number | boolean>;
Пример использования:
const combine: ComboObject = {
known: true,
field: 1,
name: "name",
age: 1,
isAlive: true,
};
combine?.known; // boolean
combine?.field; // number
combine?.name; // string | number | boolean
combine?.age; // string | number | boolean
combine?.isAlive; // string | number | boolean
Таким образом, вы получаете тип ComboObject
, который включает известные свойства и позволяет добавлять любые другие свойства с типами string
, number
или boolean
.
Отличный подход, @jcalz!
Он дал мне полезное понимание для достижения того, что мне нужно. У меня есть базовый объект (BaseObject
) с некоторыми известными свойствами, и этот базовый объект может содержать неограниченное количество других базовых объектов.
type BaseObject = { known: boolean, field: number };
type CoolType<C, X extends string | number | symbol = Exclude<keyof C, keyof BaseObject>> = BaseObject & Record<X, BaseObject>;
const asComboObject = <C>(x: C & CoolType<C>): C => x;
const tooManyExtraKeys = asComboObject({
known: true,
field: 123,
unknownName: {
known: false,
field: 333
},
anAdditionalName: {
known: true,
field: 444
},
});
Таким образом, я могу получить проверки типов для структуры, которую уже имел, не внося слишком много изменений.
Спасибо!
Я столкнулся с серьезной проблемой, но наконец нашел хорошее и простое решение!
export type Overwrite<Base, Overrides> = Omit<Base, keyof Overrides> & Overrides;
export type LocationsType = Overwrite<
{
[key: string]: LocationType
},
{
add: (location: LocationData) => void
addMultiple: (locationsData?: LocationsData) => void
getAllSources: (maxAmount?: number) => SourceType[]
}
>
Что это означает? Все ключи - строки с значением LocationType
, кроме ключей в Overrides
. Эти конкретные строки имеют заданные значения.
Больше не нужно использовать конструкцию { [key: string]: LocationType | () => void | (someParam: string) => void | any }
🎉🎉🎉
Для полноты картины, это всего лишь небольшое изменение, так как я уже использовал Overwrite
для этого шаблона:
const customerKeys = ['id', 'firstName', 'lastName', 'birthDate'] as const
type CustomerKey = typeof customerKeys[number]
export type CustomerData = Record<CustomerKey, string>
export type CustomerType = Overwrite<
CustomerData,
{
id: number
birthDate: Date
locations: LocationsType
}
>
Это типизирует JSON-ответ и разобранный результат, который можно использовать в качестве данных и типа.
Опциональные параметры на основе условных типов
Разница между "require(x)" и "import x"
@Directive против @Component в Angular
ESLint: 8.0.0 Не удалось загрузить плагин '@typescript-eslint'
Как создать заглушку для интерфейса / определения типа в TypeScript?