Производительность React за пределами ручного использования memo: структура и планирование
Советы времен компиляторов: размещайте состояние в одном месте, отделяйте срочные задачи, отправляйте меньше кода на JavaScript, а затем храните в кэше выявленные «горячие» участки.
В течение многих лет рекомендации по повышению производительности React заключались в том, чтобы оборачивать всё с помощью API memo:
const value = useMemo(() => expensiveCalculation(data), [data]);
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);export default React.memo(Component);
function ProductList({ products, onSelect }) {
const processed = useMemo(
() => processProducts(products),
[products]
);
const handleSelect = useCallback(
(id) => onSelect(id),
[onSelect]
); return (
<List
products={processed}
onSelect={handleSelect}
/>
);
}
App
│
├── Header
├── Sidebar
├── Search
├── ProductList
└── Footer
function App() {
const [query, setQuery] = useState("");
// large component tree
}
App
│
├── Header
├── Sidebar
├── Search
│ └── query state
├── ProductList
└── Footer
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
function handleChange(event) {
const value = event.target.value; setQuery(value); startTransition(() => {
setSearchResults(filterProducts(value));
});
}
User input
│
├── urgent ───────► keep UI responsive
│
└── non-urgent ───► transition
const AnalyticsDashboard = lazy(
() => import("./AnalyticsDashboard")
);
Initial JavaScript
│
▼
┌─────────────────────┐
│ Everything │
│ Dashboard │
│ Analytics │
│ Editor │
│ Admin │
└─────────────────────┘
Initial load
│
├── Core UI
│
└── Later
├── Analytics
├── Editor
└── Admin
"This component renders often."
│
▼
Add useMemo
"This component renders often."
│
▼
Why?
│
┌──────┼───────────┐
▼ ▼ ▼
State Expensive Large
flow work subtree
│ │ │
▼ ▼ ▼
Colocate Optimize Restructure
1. Measure
↓
2. Find the expensive work
↓
3. Fix component architecture
↓
4. Reduce unnecessary JavaScript
↓
5. Prioritize updates correctly
↓
6. Let React Compiler handle memoization
↓
7. Manually optimize only when evidence says you should
Одного только мемоизации недостаточно для устранения проблем с медленной работой приложений. Даже идеально мемоизированная структура может работать медленно из-за чрезмерного объема состояния, срочных обновлений, выполняющих несрочные операции, или тяжелых задач на основном потоке JavaScript.
React Compiler меняет стандартные практики
Компилятор автоматизирует создание множества слотов для мемоизации. Это позволяет сосредоточить усилия разработчиков на структуре кода и планировании выполнения операций, а не на ручном добавлении функций useMemo во все места.
Во-первых: размещайте состояние ближе к местам его использования
Высокий уровень состояния в дереве приводит к повторной отрисовке крупных поддеревьев. Размещайте состояние рядом с листьями, которые в нем нуждаются; поднимайте его только тогда, когда это необходимо для совместного использования.
Второе: не заставляйте срочные обновления выполнять незначимую работу
Сохраняйте отзывчивость ввода и анимаций. Откладывайте незначимую работу с использованием переходов и отложенных значений, чтобы нажатия клавиш не блокировались дорогостоящими фильтрами.
Третье: изучайте JavaScript, который вы отправляете
Большие циклы синхронизации, дорогостоящие форматтеры и бесконечные списки оказывают сильное влияние ещё до того, как сработает механизм согласования React. Анализируйте JavaScript, а не только результаты работы React DevTools.
Так когда же следует использовать useMemo?
Он по-прежнему полезен для обеспечения стабильности ссылок между дочерними элементами, которые могут игнорировать пропсы, а также для выполнения действительно ресурсоемких чистых вычислений — после их оценки. Не используйте его как стандартное решение для всех значений.
Новый подход к производительности в React
Структура → планирование → отправка с меньшим количеством JS → затем микро-запоминание часто используемых участков. Запоминание с помощью компилятора является вспомогательным инструментом, а не заменой хорошему проектированию.
Необходимо добавить бюджет на производительность к патчам: время до следующей перерисовки для основной формы, а также снимок профиля React для известных ресурсоемких маршрутов.
Списки должны виртуализироваться до того, как будет запоминаться каждая строка. Запоминание данных у списков с 10 тысячами строк всё равно приводит к потерям производительности.
Необходимо добавить бюджет на производительность к патчам: время до следующей перерисовки для основной формы, а также снимок профиля React для известных ресурсоемких маршрутов.
Списки должны виртуализироваться до того, как будет запоминаться каждая строка. Запоминание данных у списков с 10 тысячами строк всё равно приводит к потерям производительности.
Необходимо добавить бюджет на производительность к патчам: время до следующей перерисовки для основной формы, а также снимок профиля React для известных ресурсоемких маршрутов.
Списки должны виртуализироваться до того, как будет запоминаться каждая строка. Запоминание данных у списков с 10 тысячами строк всё равно приводит к потерям производительности.
Добавьте бюджет производительности в PR-запросы: время до следующей перерисовки для основной формы и снимок профиля React для известного тяжелого маршрута.
Списки должны виртуализироваться перед тем, как сохранять кэш каждой строки. Использование кэширования при загрузке списка из 10 тысяч строк всё равно приводит к потерям производительности.
Добавьте бюджет производительности в PR-запросы: время до следующей перерисовки для основной формы и снимок профиля React для известного тяжелого маршрута.
Списки должны виртуализироваться перед тем, как сохранять кэш каждой строки. Использование кэширования при загрузке списка из 10 тысяч строк всё равно приводит к потерям производительности.
Добавьте бюджет производительности в PR-запросы: время до следующей перерисовки для основной формы и снимок профиля React для известного тяжелого маршрута.
Списки должны виртуализироваться перед тем, как сохранять кэш каждой строки. Использование кэширования при загрузке списка из 10 тысяч строк всё равно приводит к потерям производительности.