사용자 정의 표시를 위해 형식화된 숫자의 개별 부분을 가져오는 방법

형식화된 숫자를 구성 요소로 분해하여 사용자 정의 스타일을 적용하고 복잡한 인터페이스를 구축하세요

소개

format() 메서드는 "$1,234.56" 또는 "1.5M"과 같은 완전히 형식화된 문자열을 반환합니다. 이는 간단한 표시에는 적합하지만, 개별 부분을 다르게 스타일링할 수 없습니다. 통화 기호를 굵게 만들거나, 소수 부분에 다른 색상을 적용하거나, 특정 구성 요소에 사용자 정의 마크업을 적용할 수 없습니다.

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

형식화된 문자열을 사용자 정의하기 어려운 이유

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

통화 기호를 다른 색상으로 표시하는 금액을 보여주는 대시보드를 생각해 보세요. format()를 사용하면 다음을 수행해야 합니다.

  1. 어떤 문자가 통화 기호인지 감지
  2. 기호와 숫자 사이의 공백 처리
  3. 로케일 전반에 걸친 다양한 기호 위치 처리
  4. 숫자를 손상시키지 않도록 문자열을 신중하게 파싱

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

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

formatToParts를 사용하여 숫자 구성 요소 가져오기

formatToParts() 메서드는 반환 값을 제외하고 format()와 동일하게 작동합니다. 동일한 옵션으로 포매터를 생성한 다음 format() 대신 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" }
]

각 객체는 해당 부분이 무엇을 나타내는지 식별하는 type 속성과 실제 문자열을 포함하는 value 속성을 포함합니다. 부분들은 형식화된 출력에서 나타나는 것과 동일한 순서로 표시됩니다.

모든 값을 함께 결합하여 이를 확인할 수 있습니다:

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

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

부분 유형 이해하기

type 속성은 각 구성 요소를 식별합니다. 서로 다른 형식 옵션은 서로 다른 부분 유형을 생성합니다.

기본 숫자 형식의 경우:

const formatter = new Intl.NumberFormat("en-US");
const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

integer 유형은 정수 부분을 나타냅니다. 그룹 구분 기호가 숫자를 분할할 때 여러 개의 integer 부분이 나타납니다. group 유형은 천 단위 구분 기호를 나타냅니다. decimal 유형은 소수점을 나타냅니다. fraction 유형은 소수점 이하의 숫자를 나타냅니다.

통화 형식의 경우:

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

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 유형은 로케일 규칙에 따라 숫자 앞이나 뒤에 나타납니다.

백분율의 경우:

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

const parts = formatter.formatToParts(0.1234);
console.log(parts);
// [
//   { type: "integer", value: "12" },
//   { type: "percentSign", value: "%" }
// ]

percentSign 유형은 퍼센트 기호를 나타냅니다.

간결한 표기법의 경우:

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

const parts = formatter.formatToParts(1500000);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "5" },
//   { type: "compact", value: "M" }
// ]

compact 유형은 K, M 또는 B와 같은 크기 표시자를 나타냅니다.

숫자 부분에 사용자 정의 스타일 적용하기

formatToParts()의 주요 사용 사례는 서로 다른 구성 요소에 서로 다른 스타일을 적용하는 것입니다. 부분 배열을 처리하여 특정 유형을 HTML 요소로 래핑할 수 있습니다.

통화 기호를 굵게 표시하기:

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);
// Output: "<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);
// Output: "1,234<span class="text-gray-500">.50</span>"

이 패턴은 소수 부분이 더 작게 또는 더 연하게 표시되는 가격 표시에서 일반적으로 사용됩니다.

음수를 색상으로 구분하기

금융 애플리케이션은 종종 음수를 빨간색으로 표시합니다. formatToParts()를 사용하면 마이너스 기호를 감지하고 그에 따라 스타일을 적용할 수 있습니다.

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

function formatWithColor(number) {
  const parts = formatter.formatToParts(number);
  const hasMinusSign = parts.some(part => part.type === "minusSign");

  const html = parts
    .map(part => part.value)
    .join("");

  if (hasMinusSign) {
    return `<span class="text-red-600">${html}</span>`;
  }

  return html;
}

console.log(formatWithColor(-1234.56));
// Output: "<span class="text-red-600">-$1,234.56</span>"

console.log(formatWithColor(1234.56));
// Output: "$1,234.56"

이 접근 방식은 음수 표시에 다른 기호나 위치를 사용하는 로케일에서도 음수를 안정적으로 감지합니다.

여러 스타일을 사용하여 사용자 정의 숫자 표시 구축하기

복잡한 인터페이스는 여러 스타일 규칙을 결합하는 경우가 많습니다. 다양한 part 유형에 서로 다른 클래스나 요소를 동시에 적용할 수 있습니다.

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

function formatCurrency(number) {
  const parts = formatter.formatToParts(number);

  return parts
    .map(part => {
      switch (part.type) {
        case "currency":
          return `<span class="currency-symbol">${part.value}</span>`;
        case "integer":
          return `<span class="integer">${part.value}</span>`;
        case "group":
          return `<span class="group">${part.value}</span>`;
        case "decimal":
          return `<span class="decimal">${part.value}</span>`;
        case "fraction":
          return `<span class="fraction">${part.value}</span>`;
        case "minusSign":
          return `<span class="minus">${part.value}</span>`;
        default:
          return part.value;
      }
    })
    .join("");
}

console.log(formatCurrency(1234.56));
// Output: "<span class="currency-symbol">$</span><span class="integer">1</span><span class="group">,</span><span class="integer">234</span><span class="decimal">.</span><span class="fraction">56</span>"

이러한 세밀한 제어를 통해 각 구성 요소에 대한 정확한 스타일링이 가능합니다. 그런 다음 CSS를 사용하여 각 클래스를 다르게 스타일링할 수 있습니다.

사용 가능한 모든 part 유형

type 속성은 사용된 형식 옵션에 따라 다음 값을 가질 수 있습니다.

  • integer: 정수 자릿수
  • fraction: 소수 자릿수
  • decimal: 소수 구분 기호
  • group: 천 단위 구분 기호
  • currency: 통화 기호
  • literal: 형식 지정으로 추가된 공백 또는 기타 리터럴 텍스트
  • percentSign: 퍼센트 기호
  • minusSign: 음수 표시자
  • plusSign: 양수 표시자(signDisplay가 설정된 경우)
  • unit: 단위 형식 지정을 위한 단위 문자열
  • compact: 간결한 표기법의 크기 표시자(K, M, B)
  • exponentInteger: 과학적 표기법의 지수 값
  • exponentMinusSign: 지수의 음수 기호
  • exponentSeparator: 가수와 지수를 구분하는 기호
  • infinity: 무한대 표현
  • nan: 숫자가 아님 표현
  • unknown: 인식되지 않는 토큰

모든 서식 옵션이 모든 부분 유형을 생성하는 것은 아닙니다. 수신하는 부분은 숫자 값과 포매터 구성에 따라 달라집니다.

과학적 표기법은 지수 관련 부분을 생성합니다:

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

const parts = formatter.formatToParts(1234);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "234" },
//   { type: "exponentSeparator", value: "E" },
//   { type: "exponentInteger", value: "3" }
// ]

특수 값은 특정 부분 유형을 생성합니다:

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatToParts(Infinity));
// [{ type: "infinity", value: "∞" }]

console.log(formatter.formatToParts(NaN));
// [{ type: "nan", value: "NaN" }]

접근 가능한 숫자 표시 생성

formatToParts()를 사용하여 형식화된 숫자에 접근성 속성을 추가할 수 있습니다. 이를 통해 스크린 리더가 값을 올바르게 읽을 수 있습니다.

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

function formatAccessibleCurrency(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(formatAccessibleCurrency(1234.56));
// Output: "<span aria-label="1234.56 US dollars">$1,234.56</span>"

이를 통해 스크린 리더가 서식이 지정된 표시 값과 기본 숫자 값을 적절한 컨텍스트와 함께 읽을 수 있습니다.

특정 숫자 범위 강조

일부 애플리케이션은 특정 범위에 속하는 숫자를 강조 표시합니다. formatToParts()를 사용하면 적절한 형식을 유지하면서 값에 따라 스타일을 적용할 수 있습니다.

const formatter = new Intl.NumberFormat("en-US");

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

  if (number >= threshold) {
    return `<span class="text-green-600 font-bold">${formatted}</span>`;
  }

  return formatted;
}

console.log(formatWithThreshold(1500, 1000));
// Output: "<span class="text-green-600 font-bold">1,500</span>"

console.log(formatWithThreshold(500, 1000));
// Output: "500"

숫자는 로케일에 맞는 적절한 서식을 받으며 비즈니스 로직에 따라 조건부 스타일이 적용됩니다.

formatToParts와 format 중 언제 사용할지

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

다음이 필요한 경우 formatToParts()를 사용하세요:

  • 숫자의 다른 부분에 다른 스타일 적용
  • 서식이 지정된 숫자로 HTML 또는 JSX 구축
  • 특정 구성 요소에 속성 또는 메타데이터 추가
  • 서식이 지정된 숫자를 복잡한 레이아웃에 통합
  • 서식이 지정된 출력을 프로그래밍 방식으로 처리

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

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

부분이 로케일별 형식을 유지하는 방법

parts 배열은 로케일별 형식 규칙을 자동으로 유지합니다. 로케일마다 기호를 배치하는 위치와 사용하는 구분 기호가 다르지만, 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: "€" }
// ]

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

literal 타입은 다른 카테고리에 맞지 않는 포매터가 삽입한 공백이나 텍스트를 나타냅니다. 독일어 통화 형식에서는 숫자와 통화 기호 사이의 공백을 나타냅니다.

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

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