Доки по разработке
This project is maintained by teniryte
Этот файл описывает основные приёмы диагностики и оптимизации производительности React‑приложений, а также полезные инструменты для отладки.
Первый шаг к оптимизации — понять, что именно ререндерится и когда. Простой приём — логировать рендеры компонентов.
function Message({ message }: { message: string }) {
console.log('Render Message');
return <div>{message}</div>;
}
export function PerfPage() {
const [count, setCount] = useState(0);
console.log('Render PerfPage');
return (
<>
<h1>Performance</h1>
<button onClick={() => setCount(c => c + 1)}>
COUNT: {count}
</button>
<Message message="Это сообщение" />
</>
);
}
В таком виде при каждом клике будут ререндериться и PerfPage, и Message. Это не всегда плохо, но если компонент тяжёлый, стоит подумать об оптимизации.
React.memo для мемоизации компонентовReact.memo предотвращает ререндер дочернего компонента, если его пропсы поверхностно не изменились.
const MessageMemo = React.memo(function MessageMemo({
message,
}: {
message: string;
}) {
console.log('Render MessageMemo');
return <div>{message}</div>;
});
export function PerfPageOptimized() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => c + 1)}>
COUNT: {count}
</button>
{/* Этот компонент не будет ререндериться при изменении count */}
<MessageMemo message="Статическое сообщение" />
</>
);
}
Важно: если вы передаёте функции или объекты, стоит мемоизировать и их (useCallback, useMemo), иначе React.memo не поможет.
Частая проблема — когда один useState/useReducer отвечает сразу за всё, и изменение одной части данных вызывает ререндер всего дерева.
Рекомендации:
useState вместо одного большого объекта, если части состояния редко меняются вместе;export function SplitStateExample() {
const [counter, setCounter] = useState(0);
const [filter, setFilter] = useState('');
return (
<div>
<button onClick={() => setCounter(c => c + 1)}>
Counter: {counter}
</button>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Фильтр"
/>
{/* Тяжёлый список можно мемоизировать и передавать только нужные данные */}
<HeavyList filter={filter} />
</div>
);
}
При серверном рендеринге React сначала создаёт HTML на сервере, а затем “гидрирует” его на клиенте — привязывает обработчики событий и превращает статический HTML в “живое” приложение.
Пример ручной гидратации:
import { renderToString } from 'react-dom/server';
import { hydrateRoot } from 'react-dom/client';
function App() {
return <button onClick={() => alert('Click')}>Click me</button>;
}
// На сервере:
const html = renderToString(<App />);
// → html отправляется клиенту как строка
// На клиенте:
const container = document.getElementById('root')!;
container.innerHTML = html;
hydrateRoot(container, <App />);
Советы:
React DevTools (расширение для браузера) позволяет:
Подход к профилированию:
React.memo, вынос состояния, useMemo, useCallback).Стандартная console.log остаётся простейшим способом понять, что происходит. На мобильных устройствах её сложнее смотреть, поэтому можно:
Пример крайне простой “консольки” внутри приложения:
function useLog() {
const [logs, setLogs] = useState<string[]>([]);
function log(message: string) {
setLogs(prev => [...prev, message]);
}
return { logs, log };
}
export function PageWithLogs() {
const { logs, log } = useLog();
return (
<div>
<button onClick={() => log('Clicked!')}>Click</button>
<pre style={{ maxHeight: 200, overflow: 'auto' }}>
{logs.join('\n')}
</pre>
</div>
);
}
useMemo/useCallback “на всякий случай”: это усложняет код и иногда даже замедляет его.useTransition и useDeferredValue.