Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимизация рендера в React
Рендер — это одна из самых дорогих операций в браузере. Если рендерится лишний раз, падает производительность. Давай разберемся в стратегиях.
Почему рендер медленный
Когда React перерисовывает компонент:
- Выполняются все функции компонента
- Пересчитываются все зависимости
- Обновляется DOM (если что-то изменилось)
- Браузер перерисовывает страницу (repaint, reflow)
Если вы неправильно структурируете код, родитель может триггерить перерендер 100+ дочерних компонентов просто так.
Способ 1: memo() — мемоизация компонента
Это оборачивает компонент и предотвращает перерендер если props не изменились:
// ДО: будет перерендериваться каждый раз
function UserCard({ user, onClick }) {
console.log("UserCard рендерится");
return <div onClick={onClick}>{user.name}</div>;
}
// ПОСЛЕ: будет перерендериваться только если user или onClick изменились
const UserCard = memo(function({ user, onClick }) {
console.log("UserCard рендерится");
return <div onClick={onClick}>{user.name}</div>;
});
Важно: memo сравнивает props поверхностно (shallow). Если вы передаете объект:
function Parent() {
const user = { id: 1, name: "Анна" }; // Новый объект каждый раз!
return <UserCard user={user} />; // memo не поможет
}
Вы получите перерендер всё равно, потому что user — новый объект.
Способ 2: useMemo() — мемоизация значения
Это кеширует дорогое вычисление и обновляет только если зависимости изменились:
function Parent() {
// ПЛОХО: user создается каждый раз
const user = { id: 1, name: "Анна" };
// ХОРОШО: user создается только если зависимости изменились
const user = useMemo(() => (
{ id: 1, name: "Анна" }
), []); // Пустой массив = создается один раз
return <UserCard user={user} />;
}
Реальный пример — дорогое вычисление:
function Leaderboard({ players }) {
// Сортировка 10000+ записей — дорого!
const sortedPlayers = useMemo(() => {
console.log("Сортирую...");
return [...players].sort((a, b) => b.score - a.score);
}, [players]); // Пересчитываем только если players изменились
return (
<ul>
{sortedPlayers.map(p => <li key={p.id}>{p.name}: {p.score}</li>)}
</ul>
);
}
Способ 3: useCallback() — мемоизация функции
Оборачивает функцию так, чтобы она была одна и та же в памяти, если зависимости не изменились:
function Parent() {
// ПЛОХО: onClick создается каждый раз
const onClick = () => console.log("Клик");
// ХОРОШО: onClick остается той же функцией если зависимости не изменились
const onClick = useCallback(() => {
console.log("Клик");
}, []); // Пустой массив = функция создается один раз
return <UserCard onClick={onClick} />;
}
Когда это нужно:
function Parent() {
const [count, setCount] = useState(0);
// ПРОБЛЕМА: onClick меняется каждый раз, memo(Button) перерендеривается
const handleClick = () => setCount(count + 1);
// РЕШЕНИЕ: callback запоминает count через замыкание
const handleClick = useCallback(() => {
setCount(c => c + 1); // Используем функцию-обновление
}, []);
return <Button onClick={handleClick} />;
}
Способ 4: Правильная структура компонентов
Перемещайте state ближе к месту использования:
// ПЛОХО: весь state в корне, все дети перерендеривются
function App() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [filters, setFilters] = useState({});
return (
<div>
<SearchBox query={query} onChange={setQuery} />
<FilterPanel filters={filters} onChange={setFilters} />
<ResultsList results={results} />
</div>
);
}
// ХОРОШО: состояние разделено
function SearchSection() {
const [query, setQuery] = useState("");
return <SearchBox query={query} onChange={setQuery} />;
}
function FilterSection() {
const [filters, setFilters] = useState({});
return <FilterPanel filters={filters} onChange={setFilters} />;
}
function App() {
const [results, setResults] = useState([]);
return (
<div>
<SearchSection />
<FilterSection />
<ResultsList results={results} />
</div>
);
}
Теперь изменение query не влияет на FilterSection и ResultsList.
Способ 5: Разделение state в Context
Если вам нужен глобальный state, разделите его:
// ПЛОХО: один Context с всем
const AppContext = createContext({
theme: "light",
language: "ru",
user: null,
notifications: []
});
// ХОРОШО: отдельные Contexts
const ThemeContext = createContext("light");
const UserContext = createContext(null);
const NotificationsContext = createContext([]);
Теперь компонент, использующий только theme, не перерендеривается при изменении notifications.
Способ 6: Виртуализация длинных списков
Не рендерьте все элементы списка, только видимые:
import { FixedSizeList } from 'react-window';
function LongList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)}
</FixedSizeList>
);
}
Вместо рендера 10000 элементов, рендерим только 30 видимых на экране.
Способ 7: Ленивая загрузка компонентов
Загружайте тяжелые компоненты только когда они нужны:
const HeavyEditor = lazy(() => import('./HeavyEditor'));
function App() {
const [showEditor, setShowEditor] = useState(false);
return (
<div>
<button onClick={() => setShowEditor(true)}>Открыть редактор</button>
{showEditor && (
<Suspense fallback={<div>Загружается...</div>}>
<HeavyEditor />
</Suspense>
)}
</div>
);
}
Способ 8: Правильное использование ключей в списках
// ПЛОХО: индекс как ключ (при удалении элемента всё перепутается)
list.map((item, index) => <Item key={index} item={item} />)
// ХОРОШО: уникальный ID
list.map(item => <Item key={item.id} item={item} />)
Инструменты для анализа
React DevTools Profiler:
- Откройте DevTools -> Profiler
- Запишите сессию
- Посмотрите, какие компоненты рендерились и почему
console.log с условиями:
function UserCard({ user }) {
if (process.env.NODE_ENV === 'development') {
console.log("UserCard рендерится", user.id);
}
return <div>{user.name}</div>;
}
Чеклист оптимизации
- Используй memo() для компонентов с дорогим рендером
- Используй useMemo() для дорогих вычислений
- Используй useCallback() для колбэков, переданных в memo() компоненты
- Раздели state по блокам (не держи всё в корне)
- Раздели Context если у тебя много значений
- Используй react-window для длинных списков
- Ленивая загрузка тяжелых компонентов
- Правильные ключи в списках
Помни: оптимизация нужна только если есть проблема. Профилируй сначала, оптимизируй потом.