챌린지

블록, 엘리먼트, 모디파이어 방법론(BEM)은 CSS에서 인기 있는 클래스 네이밍 방법 중 하나입니다.

예를 들어, 블록 컴포넌트는 btn으로 표현될 수 있고, 그 블록에 의존적인 엘리먼트 는 btn__price가 될 수 있습니다. 블록의 스타일을 변화시키는 모디파이어의 경우 btn--big이나 btn__price--warning으로 표현합니다.

세 개의 매개변수를 가지고 문자열 유니온을 만드는 BEM<B, E, M>을 구현해보세요. B는 문자열이고, EM은 문자열 배열이거나 (빈 배열일 수) 있습니다.

해답

이번 챌린지에서는 주어진 조건에 따라 어떤 문자열을 만들어야 합니다. 따라야 하는 조건은 블록, 엘리먼트, 모디파이어 3가지 입니다. 풀이의 형태를 단순하게 하기 위해 세 개의 개별적인 타입으로 분리할 것을 제안하고 싶습니다.

첫 번째인 블록부터 시작하겠습니다:

type Block<B extends string> = any;

이 타입은 입력으로 들어온 타입 매개변수를 포함하는 템플릿 리터럴 타입을 반환하기 만 하면 되어서 꽤 간단합니다:

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

다음은 엘리먼트입니다. 주어지는 엘리먼트 배열이 비었을 수도 있기 때문에 블록처럼 템플릿 리터럴 타입을 반환할 수 없습니다. 따라서 배열이 비어있는지 검사한 후에 문 자열을 구성해야 합니다. T[number]와 같이 접근할 때 빈 배열은 never 타입을 반 환하는 것을 이용하여 조건부 타입을 사용할 수 있습니다:

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]}`;

남은 것은 세 타입을 처음 만든 타입에 합치는 것입니다:

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

네 타입을 모두 합친 최종 풀이는 다음과 같습니다:

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>}`;

참고