Tagged Template Literal

작성일:2024.11.15|조회수:0

Tagged Template Literal

태그된 템플릿 리터럴은 ES6부터 도입된 자바스크립트 기능이다. 이 문법은 템플릿 리터럴을 단순한 문자열 보간 도구로 사용하는 데서 한 단계 더 나아가, 템플릿 자체를 함수로 전달하여 가공할 수 있게 한다는 점에서 의미가 크다. 즉, 문자열을 만드는 과정에 개발자가 직접 개입할 수 있는 훅(hook)을 제공하는 셈이다.

일반적인 템플릿 리터럴은 문자열을 표현하는 데 그 목적이 한정된다. ${} 내부의 표현식은 평가되어 문자열로 치환되고, 그 결과가 그대로 이어 붙여진다. 하지만 태그된 템플릿 리터럴에서는 이 과정이 자동으로 끝나지 않는다. 템플릿 리터럴 바로 앞에 위치한 태그 함수가 문자열 조합의 주도권을 가져가며, 문자열의 각 조각과 삽입된 값들을 분리된 상태로 전달받아 원하는 방식으로 처리할 수 있다.

형태만 놓고 보면 태그 함수는 일반 함수와 크게 다르지 않다. 다만 호출 방식이 독특하다. 태그 함수는 다음과 같은 시그니처를 가진다.

TS
function myTag(
  strings: TemplateStringsArray,
  ...values: unknown[]
): string {
  // ...
}

첫 번째 인자로 전달되는 strings는 템플릿 리터럴에서 고정된 문자열 조각들만 모아둔 배열이다. 그리고 ${} 안에 들어간 표현식들의 결과값은 나머지 인자(...values)로 순서대로 전달된다. 템플릿에 어떤 값이 들어올지 알 수 없기 때문에 unknown으로 선언했지만, 상황에 따라 string[], number[], 혹은 제네릭을 활용해 더 엄격한 타입으로 제한할 수도 있다.

프론트엔드 개발자라면 이 문법을 처음 보는 순간 떠오르는 대표적인 사례가 있다. 바로 styled-components의 styled 함수다. 이 함수는 태그된 템플릿 리터럴을 통해 CSS 문자열과 동적 값을 받아 React 컴포넌트를 생성한다. 또 다른 예로는 Apollo Client나 graphql-request에서 제공하는 gql 함수가 있다. 이 역시 태그된 템플릿 리터럴을 통해 GraphQL 쿼리를 파싱하고, 내부적으로 의미 있는 구조로 변환한다.

이 문법을 이해하는 데 있어 꼭 기억해두어야 할 중요한 규칙이 하나 있다. strings의 길이는 언제나 values보다 정확히 하나 더 길다는 점이다. 이는 템플릿 리터럴이 ${}를 기준으로 문자열을 나누기 때문이다. 분리 기준이 되는 토큰의 개수보다 항상 하나 더 많은 조각이 생긴다고 생각하면 이해하기 쉽다.

SH
"가나다나가".split("나"); // ["가", "다", "가"]

const 나 = "나";
myTag`가${나}다${나}가`;
// strings: ["가", "다", "가"]
// values: ["나", "나"]

이 구조 덕분에 태그 함수는 문자열을 단순히 이어 붙이는 것이 아니라, 각 값이 어떤 문맥에서 사용되었는지를 판단할 수 있다. 바로 이 지점이 태그된 템플릿 리터럴의 핵심적인 활용 포인트다.

그렇다면 이 문법은 실제로 어떤 상황에서 유용할까? 가장 먼저 떠올릴 수 있는 활용은 값 검증과 의미 부여다. ${}로 전달된 값들을 단순히 문자열로 변환하지 않고, 그 관계를 검사하거나 특정 규칙을 강제할 수 있다. 또한 객체가 그대로 문자열로 변환되어 [object Object]가 되는 문제를 피하고, 의도한 형태로 출력하도록 제어할 수도 있다.

아래 예시는 성적 정보를 서술하는 문장을 태그된 템플릿 리터럴로 처리하면서, 전달된 숫자들 간의 관계를 검증하는 예시다.

TS
function score(
  strings: TemplateStringsArray,
  name: string,
  total: number,
  korean: number,
  english: number,
  math: number
) {
  if (korean + english + math !== total) {
    throw new Error(
      "세 과목 점수의 합이 총점과 일치하지 않습니다."
    );
  }

  return strings.reduce((acc, str, i) => {
    const value = [name, total, korean, english, math][i];
    return acc + str + (value ?? "");
  }, "");
}

이 함수는 단순히 문자열을 생성하는 역할을 넘어, 도메인 규칙을 강제하는 진입점이 된다. 사용 예시는 다음과 같다.

JS
const name = "철수";
const totalPoint = 270;
const koreanPoint = 90;
const englishPoint = 90;
const mathPoint = 90;

score`학생 ${name}이 시험에서 받은 점수의 총 합은 ${totalPoint}점이며,
국어 ${koreanPoint}점, 영어 ${englishPoint}점, 수학 ${mathPoint}점을 획득했습니다.`;

이제 이 문장은 단순한 문자열이 아니다. 템플릿의 형태 자체가 하나의 인터페이스가 되고, 태그 함수는 그 인터페이스를 해석하는 로직이 된다. 값의 개수나 의미가 맞지 않으면 즉시 에러를 발생시킬 수 있고, 이는 런타임에서 조용히 잘못된 문자열을 만들어내는 것보다 훨씬 안전하다.

더 나아가면 이 패턴은 국제화(i18n), 로깅 포맷팅, 보안 처리(예: SQL injection 방지), 도메인 특화 언어(DSL) 구현 등 다양한 영역으로 확장할 수 있다. 중요한 점은 태그된 템플릿 리터럴이 “문자열을 예쁘게 만드는 문법”이 아니라, 문자열이 생성되는 순간에 의미와 제약을 부여할 수 있는 도구라는 사실이다.

태그된 템플릿 리터럴은 자바스크립트 문법 중에서도 상대적으로 덜 사용되지만, 한 번 그 구조를 이해하고 나면 단순한 문자열 처리 이상의 가능성을 가진다는 것을 느끼게 된다. 값과 문맥을 동시에 다룰 수 있다는 점에서, 이 문법은 라이브러리 설계나 추상화 계층에서 특히 강력한 무기가 된다.

더 읽어보기

댓글

댓글을 불러오는 중...