Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як комбінувати шаблонні рядкові типи з mapped types для типізованих подій і ключів?

Шаблонні рядкові типи разом з mapped types дають змогу генерувати API з опису даних - і компілятор перевіряє назви, які раніше були «просто рядками».

Обробники змін для кожного поля стану:

type State = { name: string; age: number; subscribed: boolean };

type ChangeHandlers<T> = {
  [K in keyof T & string as `on${Capitalize<K>}Change`]: (value: T[K]) => void;
};

type Handlers = ChangeHandlers<State>;
// {
//   onNameChange: (value: string) => void;
//   onAgeChange: (value: number) => void;
//   onSubscribedChange: (value: boolean) => void;
// }

Типізований емітер подій:

type Events = {
  'order:created': { orderId: number };
  'order:paid': { orderId: number; amount: number };
  'user:logout': undefined;
};

class Emitter<E extends Record<string, unknown>> {
  on<K extends keyof E & string>(event: K, handler: (payload: E[K]) => void) { /* ... */ }
  emit<K extends keyof E & string>(event: K, payload: E[K]) { /* ... */ }
}

const bus = new Emitter<Events>();
bus.on('order:paid', ({ amount }) => {});   // amount: number
bus.emit('order:payd', { orderId: 1 });     // помилка: друкарська помилка в назві

Вибір підмножини подій за префіксом:

type OrderEvents = Extract<keyof Events, `order:${string}`>;   // 'order:created' | 'order:paid'

Ключі перекладів з вкладеного об'єкта:

type Paths<T> = {
  [K in keyof T & string]: T[K] extends object ? `${K}.${Paths<T[K]>}` : K;
}[keyof T & string];

const uk = { auth: { login: 'Увійти', logout: 'Вийти' }, nav: { home: 'Головна' } };
type Key = Paths<typeof uk>;   // 'auth.login' | 'auth.logout' | 'nav.home'

declare function t(key: Key): string;
t('auth.login');    // ок
t('auth.lgoin');    // помилка

Прийом { ... }[keyof T] - обчислити mapped type і взяти об'єднання всіх значень.

Де межа корисності:

  • розмір об'єднань: сотні ключів перекладів на кілька рівнів вкладеності - сповільнення компілятора й редактора. Бібліотеки i18n часто генерують такі типи окремим кроком збирання;
  • повідомлення про помилки для складних типів бувають нечитабельними - варто мати прості іменовані проміжні типи;
  • динамічні ключі (зібрані з даних під час виконання) не перевіряються - знадобиться приведення типу чи валідація.

Де це приносить найбільшу користь: ключі перекладів, назви подій і каналів, маршрути, CSS-змінні дизайн-системи - рядки, помилки в яких інакше знаходяться лише під час виконання.

Докладніше в документації: Вбудовані типи для рядків

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання