Как в TypeScript определить тип ключей и строго определить число и названия свойств в интерфейсе?
Сталкнулся с такой проблемой.
Есть такой интерфейс:
interface dateAccessories {
day: number | string;
month: number | string;
year: number | string;
hours: number | string;
minutes: number | string;
}
И такая функция, где этот интерфейс используется:
export default function timeFormating(dateStr: string): timeCreation {
const dateObj: Date = new Date(dateStr);
const dateAccessories: dateAccessories = {
day: dateObj.getDate(),
month: dateObj.getMonth() + 1,
year: dateObj.getFullYear(),
hours: dateObj.getHours(),
minutes: dateObj.getMinutes(),
}
for (let accessory in dateAccessories) {
let value = dateAccessories[accessory];
if (value < 10) {
dateAccessories[accessory] = `0${value}`;
}
}
return {
date: `${dateAccessories.day}.${dateAccessories.month}.${dateAccessories.year}`,
time: `${dateAccessories.hours}:${dateAccessories.minutes}`
}
}
В цикле, а имено в строке let value = dateAccessories[accessory]; ts ругается:
Элемент неявно имеет тип "any", так как выражение типа "string" не может использоваться для индексации типа "dateAccessories". В типе "dateAccessories" не обнаружена сигнатура индекса с параметром типа "string".ts(7053)
Я полистал доку и нашёл способ вылечить это вот так:
interface dateAccessories {
[key: string]: string | number;
day: number | string;
month: number | string;
year: number | string;
hours: number | string;
minutes: number | string;
}
Но в этом случае интерефейс dateAccessories перестаёт быть ограниченым, т.е. в объект этого интерфейса можно добавлять любые свойства, и ts не будет ругатся, если туда ещё что-нибудь попадёт, главное чтобы указаные свойства присутствовали.
Подскажите, как надо написать интерфейс, чтобы тип его ключей был опредёл, и чтобы он был строго ограничен конкретными свойствами, которые должны в него попасть, не больше не меньше?
Ответы (1 шт):
Актуально для текущей версии TS 4.6.1
Здравствуйте! Проблема не в вашем интерфейсе, а в том, что typescript в конструкции цикла for...in, обходя ключи объекта dateAccessories не определяет их как key of данного объекта и считает просто string. Как вы понимаете, с точки зрения TS, просто строка не может подойти как ключ для объекта, т.к. это может привести к потенциальным ошибкам. Ключом может послужить только конкретная строка
Подобная проблема справедлива так же и для альтернативы итерации по ключам объекта в виде Object.keys
Самым простым решением будет указать явно для TS, что переменная accessory является именно одним из ключей dateAccessories интерфейса
for (let accessory in dateAccessories) {
let value = dateAccessories[accessory as keyof dateAccessories];
}
В случае если вы решите переписать цикл с использованием Object.keys, то явный каст может выглядеть вот таким образом:
(Object.keys(dateAccessories) as Array<keyof typeof dateAccessories>).map(key => dateAccessories[key])
Решение с Object.keys можно улучшить, создав типизированную версию данной функции вот таким способом:
declare global {
interface ObjectConstructor {
typedKeys<T>(obj: T): Array<keyof T>
}
}
Object.typedKeys = Object.keys as any
/** Теперь можно получать ключи без явного приведения к типу каждый раз */
Object.typedKeys(dateAccessories).map(key => dateAccessories[key])