+4

Hết Thời Viết Regex Định Dạng Tiền: Làm Chủ Number.toLocaleString Chuẩn ES2020 Trong TypeScript

Chào anh em Viblo

Có một bài toán "quốc dân" mà bất kỳ anh em nào làm Frontend hay làm các tính năng xuất báo cáo Backend đều phải gặp : Định dạng hiện thị con số.

  • Ở Việt Nam, một số triệu phải viết là 1.000.000 (dùng dấu chấm phân cách hàng nghìn) kèm chữ đ hoặc VND
  • Ở Mỹ, nó phải là 1,000,000.00 (dùng dấu phẩy phân cách hàng nghìn, dấu chấm cho thập phân) kèm ksy hiệu $.
  • Đau đầu hơn, dạo này các sếp lại thích hiện thị kiểu thu gọn (Compact natation) như trên Tiktok, Facebook" 1.500.000 biến thành 1.5M hoặc 1,5 Tr

Hồi mới đi làm, mình đã cặm cụi ngồi viết một đống hàm Regular Expression (Regex) để tự parse chuỗi, thêm dấu chấm phẩy. Code chạy được đấy, nhưng đến khi dự án scale sang thi trường nước ngoài thì đống Regex đó chính thức thành một bãi rác logic.

Sau đó, khi mò vào file định nghĩa kiểu mẫu của TypeScript core, mình thấy dòng code này:

/// <reference lib="es2020.intl" />

interface Number {
    toLocaleString(locales?: Intl.LocalesArgument, options?: Intl.NumberFormatOptions): string;
}

Hóa ra, JavaScript native từ bản ES2020 đã cung cấp một vũ khí tối tân nằm ngay trong lõi ngôn ngữ để cân hết mọi loại định dạng trên thế giới mà không tốn một dòng Regex nào. Hôm nay, hãy cùng mình bóc tách dòng định nghĩa này nhé!

1. Ý nghĩa của lệnh /// <reference lib="es2020.intl" />

Dòng đầu tiên trông như một comment thông thường, nhưng thực chất nó là một Triple-Slash Directive (Chỉ thị ba dấy gạch chéo) của TypeScript.

Nó ra lệnh cho compiler của TypeScript rằng: "Hãy nạp thêm toàn bộ thư viện định nghĩa kiểu mẫy về Quốc tế hóa (Internationlization - Intl) của chuẩn ES2020 vào đây". Nhờ dòng này, khi anh em gõ tham số options, VS Code sẽ tự động hiện thị gợi ý, (Autocompletion) cực kỳ chi tiết cho các thuộc tính như style, currency, notation ... giúp anh em không bao giờ sợ gõ sai chính tả.

2. Mổ xẻ hai tham số quyền lực: localesoptions

Hàm .toLocaleString() nhận vào hai tham số tùy chọn:

  • locales (Mã quốc gia/ngôn ngữ): Là một chuỗi hoặc một mảng các chuỗi chuẩn BCP 47 (Ví dụ: 'vi-VN' cho Việt Nam, 'en-US' cho Mỹ, 'ja-JP' cho Nhật Bản). Nếu anh em bỏ trống, JavaScript sẽ tự động lấy cấu hình ngôn ngữ mặc định của hệ điều hành/trình duyệt mà user đang dùng.
  • options (Intl.NumberFormatOptions): Đây mới là nơi chứa "ma thuật". Nó là một object chứa các cấu hình giúp bạn định hình con số đầu ra.

3. Những tuyệt chiêu thực chiến không cần Regex

Hãy cùng xem sức mạnh của options chuẩn ES2020 giải quyết các bài toán đau đầu trong thực tế gọn gàng như thế nào:

Tuyệt chiêu 1: Định dạng tiền tệ (Currency) chuẩn chỉnh Bạn không cần quan tâm ký hiệu tiền tệ đặt trước hay đặt sau, dấu cách thế nào, DB cứ ném ra số thuần, việc còn lại để toLocaleString lo:

const amount = 1500000;

// Định dạng kiểu Việt Nam
console.log(amount.toLocaleString('vi-VN', { style: 'currency', currency: 'VND' })); 
// 👉 Kết quả: 1.500.000 ₫

// Định dạng kiểu Mỹ
console.log(amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })); 
// 👉 Kết quả: $1,500,000.00

Tuyệt chiêu 2: Định dạng thu gọn kiểu "TikTok" (notation: 'compact') Đây chính là tính năng "đáng tiền" nhất của chuẩn ES2020. Bạn muốn rút gọn số lớn thành dạng chữ? Quá đơn giản!

const views = 1250000;

// Rút gọn kiểu Mỹ
console.log(views.toLocaleString('en-US', { notation: 'compact' })); 
// 👉 Kết quả: 1.3M

// Rút gọn kiểu Việt Nam (Tự động dịch sang chữ "Tr" hoặc "Nghìn")
console.log(views.toLocaleString('vi-VN', { notation: 'compact' })); 
// 👉 Kết quả: 1,3 Tr

Tuyệt chiêu 3: Kiểm soát số chữ số thập phân (maximumFractionDigits) Khi tính toán phần trăm hoặc chia tỉ lệ, số hay bị lẻ kiểu 33.333333333%. Muốn lấy đúng 2 chữ số sau dấu phẩy và làm tròn tự động?

const percent = 0.333333;
console.log(percent.toLocaleString('vi-VN', { 
    style: 'percent', 
    maximumFractionDigits: 2 
})); 
// 👉 Kết quả: 33,33%
  1. Vết sẹo thực chiến: Cạm bẫy hiệu năng (Performance Bottleneck) 🚨 Mặc dù toLocaleString cực kỳ tiện lợi và mạnh mẽ, nhưng nếu anh em dùng nó sai cách, nó sẽ biến thành một kẻ hủy diệt hiệu năng hệ thống.

Sai lầm kinh điển: Bỏ .toLocaleString() vào trong vòng lặp duyệt qua mảng hàng vạn dòng dữ liệu (ví dụ render bảng báo cáo tài chính).

// ❌ CODE NGUY HIỂM: Làm chậm hệ thống
largeArray.forEach(item => {
   item.formattedPrice = item.price.toLocaleString('vi-VN', { style: 'currency', currency: 'VND' });
});

Tại sao lại chậm? Vì mỗi khi hàm .toLocaleString() được gọi, JavaScript Engine phải khởi tạo lại toàn bộ cấu trúc định dạng và tra cứu ngôn ngữ (Locale data) nội bộ ở tầng cánh gà. Gọi nó 10.000 lần nghĩa là bạn bắt CPU tạo và hủy object 10.000 lần.

Giải pháp chuẩn Senior: Sử dụng Intl.NumberFormat tái sử dụng Để tối ưu tốc độ, bạn hãy khởi tạo một instance duy nhất của bộ định dạng ở ngoài vòng lặp, sau đó chỉ dùng hàm .format() của nó để bào dữ liệu. Cách này giúp tốc độ chạy nhanh gấp 10 đến 20 lần!

// ✅ CODE CHUẨN SENIOR: Khởi tạo một lần, xài mãi mãi
const VNDFormatter = new Intl.NumberFormat('vi-VN', { 
    style: 'currency', 
    currency: 'VND' 
});

largeArray.forEach(item => {
   // Tốc độ bàn thờ vì không phải khởi tạo lại cấu trúc định dạng
   item.formattedPrice = VNDFormatter.format(item.price); 
});

Đúc kết lại

Đoạn code định nghĩa interface Number với toLocaleString chuẩn ES2020 chính là lời khẳng định rằng ngôn ngữ JavaScript ngày càng trưởng thành và hỗ trợ dev tận răng. Hãy loại bỏ ngay những đoạn code regex tự chế phức tạp và dễ lỗi sang một bên, thay vào đó hãy tin dùng sức mạnh native của Intl để code của bạn vừa sạch, vừa chuẩn Type-safe, lại chuẩn quốc tế.

Cảm ơn anh em đã theo dõi bài viết này. Hãy chia sẻ cho team của mình nếu thấy hữu ích nhé! Happy Coding!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí