В экосистеме JavaScript и TypeScript термин type guards
обозначает механизмы уточнения типа значения в рантайме. При работе с
библиотекой Globalize это особенно важно из-за того, что форматирование
и парсинг локализованных данных часто принимают значения неопределённого
или широкого типа:
string | number | Date | null | unknown.
Globalize не накладывает строгих ограничений на входные данные на уровне исполнения, поэтому контроль типов переносится в прикладной код. Type guards становятся ключевым инструментом для безопасного взаимодействия с форматтерами и парсерами.
Основные функции Globalize работают с тремя категориями данных:
number)Date)string, при парсинге)Примеры API:
Globalize.numberFormatter()Globalize.dateFormatter()Globalize.parseNumber()Globalize.parseDate()Типичная проблема заключается в том, что источники данных не
гарантируют корректный тип. Например, значение может приходить из API
как unknown:
const valueFromApi: unknown = getValue();
Перед передачей в Globalize требуется уточнение типа.
Наиболее распространённый guard — проверка на число:
function isNumber(value: unknown): value is number {
return typeof value === "number" && !Number.isNaN(value);
}
Использование в контексте Globalize:
const formatter = Globalize.numberFormatter({ maximumFractionDigits: 2 });
function formatSafe(value: unknown) {
if (isNumber(value)) {
return formatter(value);
}
return "";
}
Особенность: Globalize ожидает корректный number, иначе
поведение может быть непредсказуемым (например, строковая конкатенация
или NaN в результате форматирования).
При работе с parseNumber входные данные должны быть
строкой:
function isString(value: unknown): value is string {
return typeof value === "string";
}
Пример безопасного парсинга:
const parser = Globalize.numberParser();
function parseSafe(value: unknown) {
if (isString(value)) {
return parser(value);
}
return null;
}
Парсинг чисел в локализованных форматах зависит от текущей культуры
(locale), поэтому некорректный тип строки приводит к
ошибкам интерпретации.
Globalize работает с датами через объекты Date, поэтому
требуется строгая проверка:
function isDate(value: unknown): value is Date {
return value instanceof Date && !isNaN(value.getTime());
}
Использование:
const dateFormatter = Globalize.dateFormatter({ datetime: "medium" });
function formatDateSafe(value: unknown) {
if (isDate(value)) {
return dateFormatter(value);
}
return "";
}
В TypeScript часто применяется объединённый тип:
type Input = string | number | Date | null | undefined;
Комбинированные type guards позволяют последовательно сузить тип:
function isValidInput(value: unknown): value is string | number | Date {
return isString(value) || isNumber(value) || isDate(value);
}
Дальнейшая обработка:
function format(value: unknown) {
if (!isValidInput(value)) {
return "";
}
if (isNumber(value)) {
return Globalize.numberFormatter()(value);
}
if (isString(value)) {
return Globalize.numberParser()(value);
}
return Globalize.dateFormatter()(value);
}
Такой подход отражает типичную модель работы Globalize: один API, несколько доменных типов данных.
Парсеры Globalize могут возвращать number | null или
Date | null, что требует дополнительной проверки:
function isParsedNumber(value: unknown): value is number {
return typeof value === "number" && !Number.isNaN(value);
}
Пример:
const parser = Globalize.numberParser();
function safeParse(value: unknown): number | null {
if (!isString(value)) return null;
const result = parser(value);
if (isParsedNumber(result)) {
return result;
}
return null;
}
При работе с форматами, зависящими от культуры (cldr,
locale), часто требуется дополнительная логика:
type LocaleNumber = string;
function isLocaleNumber(value: unknown): value is LocaleNumber {
return (
typeof value === "string" &&
/^[\d\s.,\u00A0]+$/.test(value)
);
}
Применение:
const parser = Globalize.numberParser();
function parseLocaleNumber(value: unknown) {
if (!isLocaleNumber(value)) return null;
return parser(value);
}
Globalize часто используется в цепочках:
Без type guards цепочка становится уязвимой:
const formatted = Globalize.numberFormatter()(value);
С guards:
function process(value: unknown) {
if (!isNumber(value)) return null;
const formatted = Globalize.numberFormatter()(value);
return formatted;
}
Globalize опирается на CLDR, где локализация влияет на:
Type guards в этом контексте не заменяют валидацию CLDR, но ограничивают входной домен, снижая риск некорректной интерпретации локализованных строк.
В крупных приложениях guards часто объединяются в композиции:
function isPrimitive(value: unknown): value is string | number | Date {
return isString(value) || isNumber(value) || isDate(value);
}
function isGlobalizeSafe(value: unknown): value is string | number {
return isString(value) || isNumber(value);
}
Такая структура позволяет адаптировать входные данные под разные API Globalize без дублирования логики.
Форматирование часто выносится в отдельный слой:
class FormatterService {
private numberFormatter = Globalize.numberFormatter();
private dateFormatter = Globalize.dateFormatter();
formatNumber(value: unknown) {
if (!isNumber(value)) return "";
return this.numberFormatter(value);
}
formatDate(value: unknown) {
if (!isDate(value)) return "";
return this.dateFormatter(value);
}
}
Такой подход фиксирует границы типов между бизнес-логикой и локализацией.
Без проверки типов характерны следующие проблемы:
string в numberFormatter без
парсингаNaN в результате вычисленийInvalid Date)Type guards устраняют неопределённость на границе между данными и Globalize API, обеспечивая детерминированное поведение форматтеров и парсеров.