Одним из наиболее затратных этапов в экосистеме FormatJS является разбор ICU-сообщений и их компиляция в функции форматирования. Особенно это заметно при динамической генерации сообщений или частом пересоздании объектов сообщений в React-приложениях.
Ключевая оптимизация заключается в разделении этапов парсинга и выполнения. Сообщения должны быть статичными относительно жизненного цикла приложения, чтобы исключить повторную компиляцию.
import { defineMessages } from 'react-intl';
const messages = defineMessages({
title: {
id: 'page.title',
defaultMessage: 'Добро пожаловать, {name}'
}
});
Использование defineMessages позволяет извлечь сообщения
на этапе сборки, что предотвращает повторный runtime-парсинг
ICU-деревьев.
Дополнительное ускорение достигается через предкомпиляцию сообщений с помощью CLI-инструментов FormatJS:
formatjs compile src/messages src/compiled-messages
Это переносит вычислительную нагрузку из браузера в build-step.
При использовании React с FormatJS основная проблема
производительности часто связана не с самой локализацией, а с повторным
рендерингом компонентов, содержащих intl.formatMessage.
Каждый ререндер вызывает повторный вызов форматирования, что становится критичным при списках или таблицах.
Оптимизация строится на трех уровнях:
import React, { memo } from 'react';
import { useIntl } from 'react-intl';
const UserCard = memo(function UserCard({ user }) {
const intl = useIntl();
const label = intl.formatMessage(
{ id: 'user.label' },
{ name: user.name }
);
return <div>{label}</div>;
});
Использование React.memo предотвращает повторный рендер
при неизменных props, снижая нагрузку на форматирование.
Частая ошибка — создание новых объектов сообщений внутри render-функции. Это приводит к тому, что FormatJS воспринимает сообщение как новое и выполняет повторный парсинг.
Проблемный паттерн:
intl.formatMessage(
{ id: 'order.total' },
{ value: total }
);
Оптимизированный подход:
const ORDER_TOTAL = {
id: 'order.total'
};
intl.formatMessage(ORDER_TOTAL, { value: total });
Стабильные ссылки позволяют движку переиспользовать кэшированные структуры.
FormatJS использует нативные API браузера
(Intl.NumberFormat, Intl.DateTimeFormat). Их
создание является дорогой операцией.
Основная стратегия оптимизации — повторное использование экземпляров форматтеров.
const numberFormatter = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
});
numberFormatter.format(12345);
В React-приложениях такие экземпляры должны создаваться вне
компонентов или мемоизироваться через useMemo.
const formatter = useMemo(
() =>
new Intl.DateTimeFormat('ru-RU', {
dateStyle: 'long'
}),
[]
);
IntlProvider в экосистеме FormatJS использует React
Context. Любое изменение props провайдера приводит к повторному рендеру
всего поддерева.
Основная ошибка — передача новых объектов messages на
каждом рендере верхнего уровня приложения.
Антипаттерн:
<IntlProvider messages={{ hello: 'Привет' }}>
Каждое обновление создаёт новый объект и вызывает cascade-render.
Оптимизация заключается в вынесении сообщений за пределы компонента:
const messages = {
hello: 'Привет'
};
<IntlProvider messages={messages}>
При необходимости динамической загрузки применяется кэширование по локали.
При большом количестве локалей загрузка всех переводов одновременно становится узким местом.
Стратегия оптимизации — ленивое подключение сообщений:
const loadMessages = async (locale) => {
switch (locale) {
case 'ru':
return import('./lang/ru.json');
case 'en':
return import('./lang/en.json');
}
};
Интеграция с динамическим импортом позволяет уменьшить initial bundle size и ускорить TTI.
Сложные ICU-конструкции (pluralization, select, nested rules) увеличивают стоимость парсинга и выполнения.
Пример тяжёлого выражения:
{
"id": "cart.items",
"defaultMessage": "{count, plural, one {# товар} few {# товара} many {# товаров} other {# товара}}"
}
Оптимизация достигается через:
const label = count === 1 ? 'товар' : 'товаров';
При отображении больших списков основная проблема — повторное форматирование одинаковых значений.
Решение — мемоизация результатов форматирования:
const cache = new Map();
function formatCached(intl, id, values) {
const key = id + JSON.stringify(values);
if (cache.has(key)) return cache.get(key);
const result = intl.formatMessage({ id }, values);
cache.set(key, result);
return result;
}
Такой подход уменьшает количество вызовов ICU-движка при повторяющихся данных.
Форматирование дат является одной из самых дорогих операций в
Intl.DateTimeFormat.
При высокой частоте обновлений (например, таймеры или ленты событий) применяется стратегия редкого пересчёта.
const now = Date.now();
if (now - lastUpdate > 60000) {
formattedDate = formatter.format(now);
lastUpdate = now;
}
Также используется разделение «сырых» и «отформатированных» данных, чтобы не вызывать форматирование на каждом рендере.
Одна из ключевых проблем производительности — смешивание форматирования и представления.
Оптимизированная архитектура предполагает выделение слоя подготовки данных:
function prepareUserView(user, intl) {
return {
name: user.name,
balance: intl.formatNumber(user.balance),
createdAt: intl.formatDate(user.createdAt)
};
}
UI-компоненты получают уже готовые строки, избегая лишних вызовов
intl во время рендера.
Каждая локаль увеличивает размер бандла и нагрузку на память при переключении.
Оптимизация включает:
const localeCache = new Map();
async function getMessages(locale) {
if (!localeCache.has(locale)) {
const messages = await import(`./lang/${locale}.json`);
localeCache.set(locale, messages);
}
return localeCache.get(locale);
}
Каждый вызов useIntl() внутри множества компонентов
может приводить к лишним подпискам на context.
Оптимизация заключается в выносе форматтера на уровень выше и передаче через props, когда компонент является глубоко вложенным и часто перерисовывается без изменения локали.
function Parent() {
const intl = useIntl();
return <Child format={intl.formatMessage} />;
}
Это уменьшает количество зависимостей от React Context и снижает вероятность каскадных обновлений.
При больших системах локализации ключи сообщений часто генерируются динамически, что приводит к невозможности кэширования.
Нормализованный подход требует строгой схемы идентификаторов:
module.section.element.state
Пример:
user.profile.button.save
Стабильность идентификаторов напрямую влияет на эффективность кэширования в FormatJS runtime и build-time оптимизации.