스타일링을 위해 포맷된 출력을 조각으로 분할하는 방법

formatToParts()를 사용하여 맞춤 스타일링을 위한 포맷된 출력의 개별 구성 요소에 접근하세요

소개

JavaScript 포맷터의 format() 메서드는 "$1,234.56" 또는 "January 15, 2025"와 같은 완전한 문자열을 반환합니다. 이는 단순 표시에는 잘 작동하지만, 개별 부분에 다른 스타일을 적용할 수 없습니다. 통화 기호를 굵게 만들거나, 월 이름에 다른 색상을 지정하거나, 특정 구성 요소에 사용자 정의 마크업을 적용할 수 없습니다.

JavaScript는 이 문제를 해결하기 위해 formatToParts() 메서드를 제공합니다. 단일 문자열을 반환하는 대신, 포맷된 출력의 각 부분을 나타내는 객체 배열을 반환합니다. 각 부분은 currency, month 또는 element와 같은 타입과 실제 문자열을 포함하는 값을 가집니다. 그런 다음 이러한 부분을 처리하여 사용자 정의 스타일을 적용하거나, 복잡한 레이아웃을 구축하거나, 포맷된 콘텐츠를 풍부한 사용자 인터페이스에 통합할 수 있습니다.

formatToParts() 메서드는 NumberFormat, DateTimeFormat, ListFormat, RelativeTimeFormat, DurationFormat을 포함한 여러 Intl 포맷터에서 사용할 수 있습니다. 이는 JavaScript의 모든 국제화 포맷팅에서 일관된 패턴을 제공합니다.

포맷된 문자열에 스타일을 쉽게 적용할 수 없는 이유

"$1,234.56"와 같은 포맷된 문자열을 받으면 통화 기호가 어디서 끝나고 숫자가 어디서 시작되는지 쉽게 식별할 수 없습니다. 다양한 로케일은 기호를 다른 위치에 배치합니다. 일부 로케일은 다른 구분 기호를 사용합니다. 이러한 문자열을 안정적으로 파싱하려면 Intl API에 이미 구현된 포맷팅 규칙을 복제하는 복잡한 로직이 필요합니다.

통화 기호를 다른 색상으로 표시하는 대시보드를 고려해 보세요. format()을 사용하면 다음과 같은 작업이 필요합니다:

  1. 어떤 문자가 통화 기호인지 감지
  2. 기호와 숫자 사이의 공백 고려
  3. 로케일 간 다른 기호 위치 처리
  4. 숫자가 손상되지 않도록 문자열을 신중하게 파싱

이 접근 방식은 취약하고 오류가 발생하기 쉽습니다. 로케일 포맷팅 규칙이 변경되면 파싱 로직이 손상됩니다.

날짜, 목록 및 기타 포맷된 출력에도 동일한 문제가 존재합니다. 로케일별 포맷팅 규칙을 다시 구현하지 않고는 구성 요소를 식별하기 위해 포맷된 문자열을 안정적으로 파싱할 수 없습니다.

formatToParts() 메서드는 구성 요소를 별도로 제공하여 이 문제를 해결합니다. 로케일에 관계없이 어떤 부분이 무엇인지 정확히 알려주는 구조화된 데이터를 받게 됩니다.

formatToParts 작동 방식

'formatToParts()' 메서드는 반환 값을 제외하고 'format()'과 동일하게 작동합니다. 동일한 옵션으로 포맷터를 생성한 다음 'format()' 대신 'formatToParts()'를 호출합니다.

이 메서드는 객체 배열을 반환합니다. 각 객체는 두 가지 속성을 포함합니다:

  • 'type': 해당 부분이 나타내는 것을 식별합니다. 예를 들어 'currency', 'month' 또는 'literal' 등이 있습니다.
  • 'value': 해당 부분의 실제 문자열을 포함합니다.

이러한 부분들은 포맷된 출력에 표시되는 것과 동일한 순서로 나타납니다. 모든 값을 함께 결합하면 'format()'을 호출한 것과 정확히 동일한 출력이 생성되는 것을 확인할 수 있습니다.

이 패턴은 'formatToParts()'를 지원하는 모든 포맷터에서 일관됩니다. 특정 부분 유형은 포맷터에 따라 다르지만 구조는 항상 동일합니다.

포맷된 숫자를 부분으로 분할하기

'NumberFormat' 포맷터는 포맷된 숫자를 분해하기 위한 'formatToParts()'를 제공합니다. 이는 기본 숫자, 통화, 백분율 및 기타 숫자 스타일에 적용됩니다.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = formatter.formatToParts(1234.56);
console.log(parts);

이는 다음과 같은 객체 배열을 출력합니다:

[
  { type: "currency", value: "$" },
  { type: "integer", value: "1" },
  { type: "group", value: "," },
  { type: "integer", value: "234" },
  { type: "decimal", value: "." },
  { type: "fraction", value: "56" }
]

각 객체는 해당 부분이 나타내는 것을 식별하고 그 값을 제공합니다. 'currency' 유형은 통화 기호를 나타냅니다. 'integer' 유형은 정수 자릿수를 나타냅니다. 'group' 유형은 천 단위 구분 기호를 나타냅니다. 'decimal' 유형은 소수점을 나타냅니다. 'fraction' 유형은 소수점 이하 자릿수를 나타냅니다.

부분들이 포맷된 출력과 일치하는지 확인할 수 있습니다:

const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// 출력: "$1,234.56"

연결된 부분들은 'format()'을 호출한 것과 정확히 동일한 출력을 생성합니다.

형식화된 숫자에서 통화 기호 스타일링

'formatToParts()'의 주요 사용 사례는 다양한 구성 요소에 다른 스타일을 적용하는 것입니다. 특정 유형을 HTML 요소로 감싸기 위해 parts 배열을 처리할 수 있습니다.

통화 기호를 굵게 만들기:

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = formatter.formatToParts(1234.56);
const html = parts
  .map(part => {
    if (part.type === "currency") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 출력: "<strong>$</strong>1,234.56"

이 접근 방식은 모든 마크업 언어에서 작동합니다. parts 배열을 처리하여 HTML, JSX 또는 다른 형식을 생성할 수 있습니다.

소수 부분을 다르게 스타일링하기:

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2
});

const parts = formatter.formatToParts(1234.5);
const html = parts
  .map(part => {
    if (part.type === "decimal" || part.type === "fraction") {
      return `<span class="text-gray-500">${part.value}</span>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 출력: "1,234<span class="text-gray-500">.50</span>"

이 패턴은 소수 부분이 더 작거나 밝게 표시되는 가격 표시에서 일반적입니다.

형식화된 날짜를 부분으로 분할하기

'DateTimeFormat' 포맷터는 형식화된 날짜와 시간을 분해하기 위한 'formatToParts()'를 제공합니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);

이는 다음과 같은 객체 배열을 출력합니다:

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2025" }
]

'month' 타입은 월 이름이나 숫자를 나타냅니다. 'day' 타입은 월의 일을 나타냅니다. 'year' 타입은 연도를 나타냅니다. 'literal' 타입은 포맷터에 의해 삽입된 공백, 구두점 또는 기타 텍스트를 나타냅니다.

형식화된 날짜에서 월 이름 스타일링하기

숫자와 동일한 패턴을 사용하여 날짜 구성 요소에 사용자 정의 스타일을 적용할 수 있습니다.

월 이름을 굵게 만들기:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "month") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 출력: "<strong>January</strong> 15, 2025"

여러 날짜 구성 요소 스타일링하기:

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const html = parts
  .map(part => {
    switch (part.type) {
      case "weekday":
        return `<span class="font-bold">${part.value}</span>`;
      case "month":
        return `<span class="text-blue-600">${part.value}</span>`;
      case "year":
        return `<span class="text-gray-500">${part.value}</span>`;
      default:
        return part.value;
    }
  })
  .join("");

console.log(html);
// 출력: "<span class="font-bold">Wednesday</span>, <span class="text-blue-600">January</span> 15, <span class="text-gray-500">2025</span>"

이러한 세분화된 제어를 통해 각 구성 요소에 대한 정밀한 스타일링이 가능합니다.

형식화된 목록을 부분으로 분할하기

ListFormat 포맷터는 형식화된 목록을 분해하기 위한 formatToParts()를 제공합니다.

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
console.log(parts);

이는 다음과 같은 객체 배열을 출력합니다:

[
  { type: "element", value: "apples" },
  { type: "literal", value: ", " },
  { type: "element", value: "oranges" },
  { type: "literal", value: ", and " },
  { type: "element", value: "bananas" }
]

element 타입은 목록의 각 항목을 나타냅니다. literal 타입은 포맷터에 의해 추가된 구분자와 접속사를 나타냅니다.

목록 항목 개별 스타일링

동일한 패턴을 사용하여 목록 요소에 사용자 정의 스타일을 적용할 수 있습니다.

목록 항목을 굵게 만들기:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
const html = parts
  .map(part => {
    if (part.type === "element") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 출력: "<strong>apples</strong>, <strong>oranges</strong>, and <strong>bananas</strong>"

특정 목록 항목 스타일링:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);

let itemIndex = 0;
const html = parts
  .map(part => {
    if (part.type === "element") {
      const currentIndex = itemIndex++;
      if (currentIndex === 0) {
        return `<span class="text-green-600">${part.value}</span>`;
      }
      return part.value;
    }
    return part.value;
  })
  .join("");

console.log(html);
// 출력: "<span class="text-green-600">apples</span>, oranges, and bananas"

이 접근 방식을 통해 로케일별 형식을 올바르게 유지하면서 특정 항목을 강조할 수 있습니다.

형식화된 상대 시간을 부분으로 분할

RelativeTimeFormat 포매터는 상대 시간 표현을 분해하기 위한 formatToParts()를 제공합니다.

const formatter = new Intl.RelativeTimeFormat("en-US", {
  numeric: "auto"
});

const parts = formatter.formatToParts(-1, "day");
console.log(parts);

이는 다음과 같은 객체 배열을 출력합니다:

[
  { type: "literal", value: "yesterday" }
]

숫자 형식의 상대 시간:

const formatter = new Intl.RelativeTimeFormat("en-US", {
  numeric: "always"
});

const parts = formatter.formatToParts(-3, "day");
console.log(parts);
// [
//   { type: "integer", value: "3" },
//   { type: "literal", value: " days ago" }
// ]

integer 타입은 숫자 값을 나타냅니다. literal 타입은 상대 시간 단위와 방향을 나타냅니다.

형식화된 기간을 부분으로 분할하기

DurationFormat 포매터는 형식화된 기간을 분해하기 위한 formatToParts()를 제공합니다.

const formatter = new Intl.DurationFormat("en-US", {
  style: "long"
});

const parts = formatter.formatToParts({
  hours: 2,
  minutes: 30,
  seconds: 15
});
console.log(parts);

이는 다음과 유사한 객체 배열을 출력합니다:

[
  { type: "integer", value: "2" },
  { type: "literal", value: " hours, " },
  { type: "integer", value: "30" },
  { type: "literal", value: " minutes, " },
  { type: "integer", value: "15" },
  { type: "literal", value: " seconds" }
]

integer 타입은 숫자 값을 나타냅니다. literal 타입은 단위 이름과 구분자를 나타냅니다.

형식화된 부분에서 HTML 구성하기

부분을 처리하고 스타일 규칙을 일관되게 적용하는 재사용 가능한 함수를 만들 수 있습니다.

function formatWithStyles(parts, styleMap) {
  return parts
    .map(part => {
      const style = styleMap[part.type];
      if (style) {
        return `<span class="${style}">${part.value}</span>`;
      }
      return part.value;
    })
    .join("");
}

const numberFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = numberFormatter.formatToParts(1234.56);
const html = formatWithStyles(parts, {
  currency: "font-bold text-gray-700",
  integer: "text-2xl",
  fraction: "text-sm text-gray-500"
});

console.log(html);
// 출력: "<span class="font-bold text-gray-700">$</span><span class="text-2xl">1</span>,<span class="text-2xl">234</span>.<span class="text-sm text-gray-500">56</span>"

이 패턴은 스타일링 규칙을 포매팅 로직과 분리하여 유지 관리 및 재사용이 더 쉽게 만듭니다.

로케일별 부분 순서 이해하기

부분 배열은 로케일별 포매팅 규칙을 자동으로 유지합니다. 다른 로케일은 구성 요소를 다른 순서로 배치하고 다른 형식을 사용하지만, formatToParts()는 이러한 차이를 처리합니다.

const usdFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

console.log(usdFormatter.formatToParts(1234.56));
// [
//   { type: "currency", value: "$" },
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

const eurFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR"
});

console.log(eurFormatter.formatToParts(1234.56));
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "." },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "," },
//   { type: "fraction", value: "56" },
//   { type: "literal", value: " " },
//   { type: "currency", value: "€" }
// ]

독일어 포매팅은 통화 기호를 숫자 뒤에 공백과 함께 배치합니다. 그룹 구분자는 마침표이고 소수점 구분자는 쉼표입니다. 스타일링 코드는 로케일에 관계없이 부분 배열을 동일한 방식으로 처리하며, 포매팅은 자동으로 조정됩니다.

접근성 있는 형식화된 디스플레이 만들기

형식화된 출력에 접근성 속성을 추가하기 위해 formatToParts()를 사용할 수 있습니다. 이는 스크린 리더가 값을 올바르게 읽을 수 있도록 도와줍니다.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatAccessible(number) {
  const parts = formatter.formatToParts(number);
  const formatted = parts.map(part => part.value).join("");

  return `<span aria-label="${number} US dollars">${formatted}</span>`;
}

console.log(formatAccessible(1234.56));
// 출력: "<span aria-label="1234.56 US dollars">$1,234.56</span>"

이렇게 하면 스크린 리더가 형식화된 표시 값과 적절한 컨텍스트가 있는 기본 숫자 값을 모두 읽을 수 있습니다.

formatToParts와 프레임워크 컴포넌트 결합하기

React와 같은 현대적인 프레임워크는 formatToParts()를 사용하여 컴포넌트를 효율적으로 구축할 수 있습니다.

function CurrencyDisplay({ value, locale, currency }) {
  const formatter = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency
  });

  const parts = formatter.formatToParts(value);

  return (
    <span className="currency-display">
      {parts.map((part, index) => {
        if (part.type === "currency") {
          return <strong key={index}>{part.value}</strong>;
        }
        if (part.type === "fraction" || part.type === "decimal") {
          return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
        }
        return <span key={index}>{part.value}</span>;
      })}
    </span>
  );
}

이 컴포넌트는 적절한 형식을 유지하면서 모든 로케일과 통화에 대해 다양한 부분에 다른 스타일을 적용합니다.

formatToParts를 사용해야 하는 경우

사용자 지정 없이 단순한 형식화된 문자열이 필요한 경우 format()을 사용하세요. 이는 대부분의 표시 시나리오에서 일반적인 경우입니다.

다음과 같은 경우에는 formatToParts()를 사용하세요:

  • 형식화된 출력의 다른 부분에 다른 스타일을 적용해야 할 때
  • 형식화된 콘텐츠로 HTML 또는 JSX를 구축해야 할 때
  • 특정 컴포넌트에 속성이나 메타데이터를 추가해야 할 때
  • 형식화된 출력을 복잡한 레이아웃에 통합해야 할 때
  • 형식화된 출력을 프로그래밍 방식으로 처리해야 할 때
  • 세밀한 제어가 필요한 사용자 정의 시각적 디자인을 만들어야 할 때

formatToParts() 메서드는 단일 문자열 대신 객체 배열을 생성하기 때문에 format()보다 약간 더 많은 오버헤드가 있습니다. 이 차이는 일반적인 애플리케이션에서는 무시할 수 있지만, 초당 수천 개의 값을 형식화하는 경우 format()이 더 좋은 성능을 보입니다.

대부분의 애플리케이션에서는 성능보다는 스타일링 요구 사항에 따라 선택하세요. 출력을 사용자 정의할 필요가 없다면 format()을 사용하세요. 사용자 정의 스타일링이나 마크업이 필요하다면 formatToParts()를 사용하세요.

포맷터 간 공통 부분 유형

서로 다른 포맷터는 다양한 부분 유형을 생성하지만, 일부 유형은 여러 포맷터에서 공통적으로 나타납니다:

  • literal: 공백, 구두점 또는 포맷팅에 의해 추가된 기타 텍스트. 날짜, 숫자, 목록 및 기간에 나타납니다.
  • integer: 정수 자릿수. 숫자, 상대 시간 및 기간에 나타납니다.
  • decimal: 소수점 구분 기호. 숫자에 나타납니다.
  • fraction: 소수 자릿수. 숫자에 나타납니다.

포맷터별 특정 유형에는 다음이 포함됩니다:

  • 숫자: currency, group, percentSign, minusSign, plusSign, unit, compact, exponentInteger
  • 날짜: weekday, era, year, month, day, hour, minute, second, dayPeriod, timeZoneName
  • 목록: element
  • 상대 시간: 숫자 값은 integer로, 텍스트는 literal로 나타납니다

이러한 유형을 이해하면 모든 포맷터 출력을 올바르게 처리하는 스타일링 코드를 작성하는 데 도움이 됩니다.