Завдання

Методологія Блок, Елемент, Модифікатор (БЕМ) — це популярне правило іменування класів у CSS.

Наприклад, компонент блоку буде представлений як btn, елемент, який залежить від блоку, буде представлений як btn__price, модифікатор, який змінює стиль блоку, буде представлений як btn--big або btn__price--warning.

Реалізуйте BEM<B, E, M>, який генеруватиме об’єднання рядків із цих трьох параметрів. Де B — рядковий літерал, E і M — це масиви рядків (можуть бути порожніми).

Розв’язок

У цьому завданні нас просять створити певний рядок, дотримуючись правил. Є 3 правила, яких ми повинні дотримуватися: Блок, Елемент і Модифікатор. Щоб спростити загальний вигляд рішення, я пропоную розділити їх на три окремі типи.

Почнемо з першого - Блок:

type Block<B extends string> = any;

Це досить просто, тому що все, що нам тут потрібно зробити, це просто повернути рядковий тип-літерал, що містить параметр вхідного типу:

type Block<B extends string> = `${B}`;

Наступний — Елемент. Це не рядковий тип-літерал, як це було з Блоком, тому що ми маємо випадок, коли масив елементів порожній. Тому нам потрібно перевірити, чи масив не порожній, і якщо так, створити рядок. Знаючи, що порожній масив повертає тип never, коли до нього звертаються як T[number], ми можемо перевірити його за допомогою умовного типу:

type Element<E extends string[]> = E[number] extends never ? never : never;

Якщо масив з елементами порожній, ми просто повертаємо порожній тип-літерал (нам не потрібен рядок із префіксом __):

type Element<E extends string[]> = E[number] extends never ? `` : never;

Коли ми знаємо, що масив не порожній, нам потрібно додати префікс __, а потім об’єднати ці елементи в тип-літерал:

type Element<E extends string[]> = E[number] extends never
  ? ``
  : `__${E[number]}`;

Ту саму логіку ми застосовуємо до останнього - Модифікатора. У випадку, якщо масив з модифікаторами порожній - повертаємо порожній тип-літерал. В іншому випадку повертаємо префікс із об’єднанням модифікаторів:

type Modifier<M extends string[]> = M[number] extends never
  ? ``
  : `--${M[number]}`;

Залишилося об’єднати ці 3 типи в наш початковий тип:

type BEM<
  B extends string,
  E extends string[],
  M extends string[],
> = `${Block<B>}${Element<E>}${Modifier<M>}`;

Повне рішення, яке включає всі 4 типи, виглядає так:

type Block<B extends string> = `${B}`;
type Element<E extends string[]> = E[number] extends never
  ? ``
  : `__${E[number]}`;
type Modifier<M extends string[]> = M[number] extends never
  ? ``
  : `--${M[number]}`;
type BEM<
  B extends string,
  E extends string[],
  M extends string[],
> = `${Block<B>}${Element<E>}${Modifier<M>}`;

Посилання