숫자에 쉼표를 직접 찍고 날짜를 문자열로 자르다가 Intl로 정리
문제 발생
금액을 표시하려고 직접 만든 함수가 통화가 늘어날 때마다 조건문이 붙었습니다.
const format = (n) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
format(123456.789); // "123,456.789" — 소수점 자리는 그대로 노출일본 엔은 소수점이 없어야 하고, 독일은 소수점과 천 단위 기호가 반대이며, 인도는 세 자리씩 끊지 않습니다. 조건문으로 담을 수 있는 규칙이 아니었습니다.
원인 분석
숫자·날짜 표기는 로케일마다 다른 규칙의 집합이고, 그 규칙은 CLDR이라는 표준 데이터로 관리됩니다. 직접 만든 포맷 함수는 그중 한 언어의 한 경우만 흉내 낼 뿐입니다.
Intl은 이 데이터를 브라우저와 런타임이 이미 갖고 있는 것으로 처리합니다.
new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(123456.789);
// "123.456,79 €"
new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(123456.789);
// "¥123,457" — 엔은 소수점이 없다다만 주의할 점이 하나 있습니다. MDN은 포매터 인스턴스를 만드는 비용이 크므로, 반복 호출에서는 한 번 만들어 재사용하라고 명시합니다. 매번 toLocaleString(...)을 호출하면 그때마다 포매터가 새로 만들어집니다.
해결 방안
- 포매터를 모듈 상단에서 한 번 만들고 재사용합니다.
const currency = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "KRW" });
items.forEach((item) => render(currency.format(item.price)));반대 예 — 목록이 길수록 손해가 커집니다.
items.forEach((item) =>
render(item.price.toLocaleString("ko-KR", { style: "currency", currency: "KRW" })),
);- 표시 목적에 맞는 옵션을 씁니다.
| 목적 | 옵션 |
|---|---|
| 통화 | style: "currency", currency: "KRW" |
| 백분율 | style: "percent" (0.25 → 25%) |
| 단위 | style: "unit", unit: "kilometer-per-hour" |
| 축약 | notation: "compact" (123456 → 123K) |
- 부분별로 스타일을 다르게 하려면
formatToParts를 씁니다. 통화 기호만 작게 표시하는 식의 디자인이 문자열 자르기 없이 가능해집니다.
currency.formatToParts(1234.5);
// [{type:"currency",...},{type:"integer",...},{type:"group",...}, ...]- 날짜에는
Intl.DateTimeFormat을 씁니다. 이 저장소도 목록에는 사람이 읽는 한국어 형식, 기계에는<time dateTime>의 ISO 문자열을 각각 제공합니다 — 두 대상의 요구가 다르기 때문입니다. - 로케일을 하드코딩하지 않습니다.
undefined를 넘기면 실행 환경의 기본 로케일을 씁니다. 서버 렌더링에서는 서버와 클라이언트의 로케일이 달라 하이드레이션이 어긋날 수 있으므로, 그 경우 로케일을 명시적으로 고정하는 편이 안전합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.