포스트

JavaScript Template Literals 완벽 가이드 - 문자열 처리의 혁명

ES6 템플릿 리터럴의 강력한 기능과 실무 활용법을 다룹니다. Tagged Templates, String 메서드, 그리고 다양한 활용 예제를 통해 현대적인 JavaScript 문자열 처리 방법을 알아봅니다.

JavaScript Template Literals 완벽 가이드 - 문자열 처리의 혁명

Template Literals란?

ES6에서 도입된 Template Literals(템플릿 리터럴)는 백틱(`)을 사용하여 문자열을 표현하는 새로운 문법입니다. 단순히 따옴표를 대체하는 것이 아니라, 문자열 보간, 여러 줄 문자열, Tagged Templates 등 강력한 기능을 제공합니다.

기본 문법

1. 백틱으로 문자열 생성

1
2
3
4
5
6
// ES5 - 따옴표
const name1 = '김개발';
const name2 = "박코딩";

// ES6 - 백틱
const name3 = `이자바`;

백틱만 사용한다면 일반 문자열과 동일합니다. 하지만 템플릿 리터럴의 진짜 힘은 다음 기능들에서 발휘됩니다.

2. 문자열 보간 (String Interpolation)

변수나 표현식을 ${} 안에 넣어 문자열에 삽입할 수 있습니다.

1
2
3
4
5
6
7
8
const name = '김개발';
const age = 28;

// ES5 - 문자열 연결 (가독성 나쁨)
const greeting1 = '안녕하세요. 저는 ' + name + '이고, ' + age + '살입니다.';

// ES6 - 템플릿 리터럴 (가독성 좋음)
const greeting2 = `안녕하세요. 저는 ${name}이고, ${age}살입니다.`;

3. 표현식 실행

${} 안에는 변수뿐만 아니라 모든 JavaScript 표현식을 넣을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const a = 10;
const b = 20;

console.log(`${a} + ${b} = ${a + b}`);
// "10 + 20 = 30"

console.log(`올해는 ${new Date().getFullYear()}년입니다.`);
// "올해는 2024년입니다."

console.log(`랜덤 숫자: ${Math.floor(Math.random() * 100)}`);
// "랜덤 숫자: 42"

// 삼항 연산자도 가능
const score = 85;
console.log(`성적: ${score >= 60 ? '합격' : '불합격'}`);
// "성적: 합격"

4. 여러 줄 문자열 (Multiline Strings)

템플릿 리터럴은 줄바꿈을 그대로 유지합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ES5 - 줄바꿈 처리 (번거로움)
const poem1 = '자세히\n' +
              '보아야\n' +
              '이쁘다\n' +
              '\n' +
              '내 코드..';

// ES6 - 템플릿 리터럴 (직관적)
const poem2 = `자세히
보아야
이쁘다

내 코드..`;

console.log(poem2);
// 자세히
// 보아야
// 이쁘다
//
// 내 코드..

String 메서드

ES6는 템플릿 리터럴과 함께 유용한 문자열 메서드도 추가했습니다.

1. startsWith() - 시작 문자열 확인

1
2
3
4
5
6
7
const email = 'yealee.kim87@gmail.com';

console.log(email.startsWith('yealee'));  // true
console.log(email.startsWith('kim'));     // false

// 시작 위치 지정 가능
console.log(email.startsWith('kim', 7));  // true (7번째부터 시작)

2. endsWith() - 끝 문자열 확인

1
2
3
4
5
6
7
const email = 'yealee.kim87@gmail.com';

console.log(email.endsWith('.com'));      // true
console.log(email.endsWith('gmail'));     // false

// 특정 길이까지만 확인
console.log(email.endsWith('@gmail', 20)); // true (앞 20자만 확인)

3. includes() - 포함 여부 확인

1
2
3
4
5
6
7
const email = 'yealee.kim87@gmail.com';

console.log(email.includes('@gmail'));    // true
console.log(email.includes('naver'));     // false

// 시작 위치 지정 가능
console.log(email.includes('kim', 10));   // false (10번째부터 검색)

4. repeat() - 문자열 반복

1
2
3
4
5
6
7
8
9
10
const star = '';
console.log(star.repeat(5));  // "⭐⭐⭐⭐⭐"

// 실용 예제: 패딩
const rating = 4;
const stars = ''.repeat(rating) + ''.repeat(5 - rating);
console.log(stars);  // "★★★★☆"

// 로딩 애니메이션
console.log('Loading' + '.'.repeat(3));  // "Loading..."

실무 활용 예제

1. HTML 템플릿 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createUserCard(user) {
  return `
    <div class="user-card">
      <h2>${user.name}</h2>
      <p class="email">${user.email}</p>
      <p class="role">${user.role || 'Member'}</p>
      <span class="status ${user.active ? 'active' : 'inactive'}">
        ${user.active ? '활성' : '비활성'}
      </span>
    </div>
  `;
}

const user = {
  name: '김개발',
  email: 'dev@example.com',
  role: 'Developer',
  active: true
};

document.body.innerHTML = createUserCard(user);

2. API URL 생성

1
2
3
4
5
6
7
8
9
10
11
function buildApiUrl(endpoint, params = {}) {
  const queryString = Object.entries(params)
    .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
    .join('&');

  return `https://api.example.com/${endpoint}${queryString ? '?' + queryString : ''}`;
}

const url = buildApiUrl('users', { page: 2, limit: 10, sort: 'name' });
console.log(url);
// "https://api.example.com/users?page=2&limit=10&sort=name"

3. 동적 클래스명 생성

1
2
3
4
5
6
7
8
9
function getButtonClass(type, size, disabled) {
  return `btn btn-${type} btn-${size}${disabled ? ' btn-disabled' : ''}`;
}

console.log(getButtonClass('primary', 'lg', false));
// "btn btn-primary btn-lg"

console.log(getButtonClass('danger', 'sm', true));
// "btn btn-danger btn-sm btn-disabled"

4. SQL 쿼리 생성 (주의: 실제로는 Prepared Statement 사용)

1
2
3
4
5
6
7
8
9
10
function buildSelectQuery(table, conditions) {
  const where = Object.entries(conditions)
    .map(([key, value]) => `${key} = '${value}'`)
    .join(' AND ');

  return `SELECT * FROM ${table}${where ? ` WHERE ${where}` : ''}`;
}

console.log(buildSelectQuery('users', { status: 'active', role: 'admin' }));
// "SELECT * FROM users WHERE status = 'active' AND role = 'admin'"

보안 경고: 실제 SQL 쿼리는 반드시 Prepared Statement를 사용하여 SQL Injection을 방지해야 합니다!

5. 로그 메시지 포맷팅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function log(level, message, data = {}) {
  const timestamp = new Date().toISOString();
  const dataStr = Object.keys(data).length > 0
    ? `\n${JSON.stringify(data, null, 2)}`
    : '';

  return `[${timestamp}] ${level.toUpperCase()}: ${message}${dataStr}`;
}

console.log(log('info', 'User logged in', { userId: 123, ip: '192.168.1.1' }));
// [2024-01-15T10:30:00.000Z] INFO: User logged in
// {
//   "userId": 123,
//   "ip": "192.168.1.1"
// }

Tagged Templates (고급)

Tagged Templates는 템플릿 리터럴을 함수로 파싱할 수 있게 해주는 강력한 기능입니다.

기본 개념

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function myTag(strings, ...values) {
  console.log('문자열 조각들:', strings);
  console.log('값들:', values);

  return strings.reduce((result, str, i) => {
    return result + str + (values[i] || '');
  }, '');
}

const name = '김개발';
const age = 28;

const result = myTag`제 이름은 ${name}이고, ${age}살입니다.`;
console.log(result);

// 출력:
// 문자열 조각들: ['제 이름은 ', '이고, ', '살입니다.']
// 값들: ['김개발', 28]
// 제 이름은 김개발이고, 28살입니다.

실용 예제 1: HTML 이스케이프

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function escapeHTML(strings, ...values) {
  const escape = (str) => String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');

  return strings.reduce((result, str, i) => {
    const value = values[i] !== undefined ? escape(values[i]) : '';
    return result + str + value;
  }, '');
}

const userInput = '<script>alert("XSS")</script>';
const safeHTML = escapeHTML`<div>${userInput}</div>`;

console.log(safeHTML);
// <div>&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</div>

실용 예제 2: 다국어 처리 (i18n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const translations = {
  ko: {
    greeting: '안녕하세요, {0}님!',
    welcome: '{0}님, {1}에 오신 것을 환영합니다!'
  },
  en: {
    greeting: 'Hello, {0}!',
    welcome: 'Welcome to {1}, {0}!'
  }
};

function i18n(lang) {
  return function(strings, ...values) {
    const key = strings.join('{0}').trim();
    let translation = translations[lang][key] || key;

    values.forEach((value, i) => {
      translation = translation.replace(`{${i}}`, value);
    });

    return translation;
  };
}

const t = i18n('ko');
console.log(t`greeting ${'김개발'}`);
// "안녕하세요, 김개발님!"

실용 예제 3: 스타일링 (styled-components 방식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function css(strings, ...values) {
  return strings.reduce((result, str, i) => {
    const value = typeof values[i] === 'function'
      ? values[i]()
      : (values[i] || '');
    return result + str + value;
  }, '');
}

const primaryColor = '#007bff';
const getSize = () => '16px';

const buttonStyle = css`
  background-color: ${primaryColor};
  font-size: ${getSize};
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
`;

console.log(buttonStyle);
// background-color: #007bff;
// font-size: 16px;
// padding: 10px 20px;
// border: none;
// border-radius: 4px;

String 메서드 실무 활용

1. 이메일 유효성 검사

1
2
3
4
5
6
7
8
9
10
function isValidEmail(email) {
  return email.includes('@') &&
         email.includes('.') &&
         email.indexOf('@') > 0 &&
         email.lastIndexOf('.') > email.indexOf('@');
}

console.log(isValidEmail('user@example.com'));  // true
console.log(isValidEmail('@example.com'));      // false
console.log(isValidEmail('user@example'));      // false

2. 파일 확장자 검사

1
2
3
4
5
6
7
8
function isImageFile(filename) {
  const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
  return imageExtensions.some(ext => filename.toLowerCase().endsWith(ext));
}

console.log(isImageFile('photo.jpg'));      // true
console.log(isImageFile('document.pdf'));   // false
console.log(isImageFile('IMAGE.PNG'));      // true

3. URL 프로토콜 확인

1
2
3
4
5
6
7
function isSecureUrl(url) {
  return url.startsWith('https://') || url.startsWith('wss://');
}

console.log(isSecureUrl('https://example.com'));  // true
console.log(isSecureUrl('http://example.com'));   // false
console.log(isSecureUrl('wss://socket.io'));      // true

4. 금지어 필터링

1
2
3
4
5
6
7
function containsBadWords(text) {
  const badWords = ['스팸', '광고', '욕설'];
  return badWords.some(word => text.includes(word));
}

console.log(containsBadWords('이건 스팸입니다'));   // true
console.log(containsBadWords('정상 메시지'));       // false

5. 진행 표시줄 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createProgressBar(progress, length = 20) {
  const filled = Math.round((progress / 100) * length);
  const empty = length - filled;

  return `[${''.repeat(filled)}${''.repeat(empty)}] ${progress}%`;
}

console.log(createProgressBar(0));
// [░░░░░░░░░░░░░░░░░░░░] 0%

console.log(createProgressBar(50));
// [██████████░░░░░░░░░░] 50%

console.log(createProgressBar(100));
// [████████████████████] 100%

비교: Template Literals vs 문자열 연결

기능문자열 연결Template Literals
가독성⭐⭐⭐⭐⭐⭐⭐
여러 줄\n 필요자동 지원
변수 삽입+ 연산자${} 간편
표현식복잡함간단함
성능약간 빠름약간 느림 (무시할 수준)
Tagged Templates불가능가능

Best Practices

✅ 좋은 사용 예

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 여러 변수를 포함한 문자열
const message = `안녕하세요, ${name}님. ${unreadCount}개의 안 읽은 메시지가 있습니다.`;

// 2. 조건부 표현
const status = `주문 상태: ${isPaid ? '결제 완료' : '결제 대기'}`;

// 3. HTML 생성
const html = `<article>${title}</article>`;

// 4. 여러 줄 문자열
const query = `
  SELECT *
  FROM users
  WHERE active = true
  ORDER BY created_at DESC
`;

❌ 피해야 할 사용 예

1
2
3
4
5
6
7
8
9
10
11
// 1. 단순 문자열은 일반 따옴표 사용
const name = `김개발`;  // ❌
const name = '김개발';   // ✅

// 2. 불필요한 표현식
const greeting = `안녕하세요${'!'}`;  // ❌
const greeting = '안녕하세요!';       // ✅

// 3. XSS 취약점 주의 (사용자 입력을 그대로 삽입)
const html = `<div>${userInput}</div>`;  // ❌ XSS 위험
const html = `<div>${escapeHTML(userInput)}</div>`;  // ✅

성능 고려사항

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 벤치마크 예제
const iterations = 1000000;
const name = 'Test';
const value = 123;

// 1. 문자열 연결
console.time('String Concatenation');
for (let i = 0; i < iterations; i++) {
  const str = 'Name: ' + name + ', Value: ' + value;
}
console.timeEnd('String Concatenation'); // ~15ms

// 2. Template Literals
console.time('Template Literals');
for (let i = 0; i < iterations; i++) {
  const str = `Name: ${name}, Value: ${value}`;
}
console.timeEnd('Template Literals'); // ~20ms

결론: Template Literals가 약간 느리지만, 실무에서는 무시할 수준입니다. 가독성과 유지보수성이 더 중요합니다!

핵심 정리

Template Literals의 장점

  1. 가독성: 변수와 표현식을 직관적으로 삽입
  2. 여러 줄 지원: 줄바꿈을 자연스럽게 표현
  3. 표현식 실행: ${} 안에서 모든 JavaScript 표현식 사용 가능
  4. Tagged Templates: 고급 문자열 처리 가능

String 메서드 정리

메서드기능반환값
startsWith(str)시작 문자열 확인Boolean
endsWith(str)끝 문자열 확인Boolean
includes(str)포함 여부 확인Boolean
repeat(n)n번 반복String

참고 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.