Зачем нужна оптимизация React-приложений
▸Проблема лишних ре-рендеров
React перерисовывает компоненты при каждом изменении состояния. Это может привести к серьезным проблемам производительности в крупных приложениях. Каждый ре-рендер выполняет JavaScript-код, обновляет DOM и потребляет ресурсы браузера.
▸Когда оптимизация действительно необходима
Не стоит оптимизировать prematurely. Используйте React DevTools Profiler для выявления реальных узких мест. Оптимизация нужна когда: компонент рендерится тысячи раз, данные для вычисления тяжелые, или дочерние компоненты перерисовываются без необходимости.
React.memo: мемоизация компонентов
▸Как работает React.memo
React.memo — это HOC, который мемоизирует результат рендера компонента. Компонент перерисовывается только если изменились пропсы.
1const ExpensiveComponent = React.memo(({ data, onClick }) => {2 console.log('Рендер ExpensiveComponent');3 return (4 <div onClick={onClick}>5 {data.map(item => <span key={item.id}>{item.name}</span>)}6 </div>7 );8});
▸Сравнение пропсов по умолчанию
По умолчанию React.memo использует поверхностное сравнение (shallow comparison) пропсов. Если пропсы — объекты или массивы, сравнение может не работать ожидаемо.
▸Кастомная функция сравнения
Для сложных пропсов передайте функцию сравнения вторым аргументом:
1const MemoizedComponent = React.memo(2 ({ items, config }) => { /* ... */ },3 (prevProps, nextProps) => {4 return prevProps.items.length === nextProps.items.length5 && prevProps.config.theme === nextProps.config.theme;6 }7);
useMemo: мемоизация вычислений
▸Проблема тяжелых вычислений
При каждом ре-рендере компонента выполняются все вычисления внутри тела функции. useMemo кэширует результат вычисления и пересчитывает только при изменении зависимостей.
1function FilteredList({ items, filter }) {2 const filteredItems = useMemo(() => {3 return items.filter(item =>4 item.name.toLowerCase().includes(filter.toLowerCase())5 );6 }, [items, filter]);78 return <List items={filteredItems} />;9}
▸Когда использовать useMemo
▸Когда не стоит использовать
Не используйте useMemo для простых вычислений — накладные расходы на мемоизацию могут превысить выгоду. Не мемоизируйте строки, числа, простые массивы без зависимости от тяжелых операций.
useCallback: мемоизация функций
▸Проблема пересоздания функций
Каждый ре-рендер создает новую ссылку на функцию. Это ломает React.memo для дочерних компонентов.
1function Parent() {2 const [count, setCount] = useState(0);34 // Эта функция пересоздается при каждом рендере5 const handleClick = useCallback(() => {6 console.log('Clicked');7 }, []);89 return <MemoizedChild onClick={handleClick} />;10}
▸useCallback vs useCallback с зависимостями
1// Без зависимостей — функция создается один раз2const handleClick = useCallback(() => {3 console.log(count);4}, []);56// С зависимостью — функция пересоздается при изменении count7const handleClick = useCallback(() => {8 console.log(count);9}, [count]);
Комбинирование memo, useMemo и useCallback
▸Полный пример оптимизации
1const DataTable = React.memo(({ data, sortConfig }) => {2 const sortedData = useMemo(() => {3 return [...data].sort((a, b) =>4 a[sortConfig.key] > b[sortConfig.key] ? 1 : -15 );6 }, [data, sortConfig]);78 const handleRowClick = useCallback((id) => {9 console.log('Row clicked:', id);10 }, []);1112 return (13 <table>14 {sortedData.map(row => (15 <TableRow16 key={row.id}17 data={row}18 onClick={handleRowClick}19 />20 ))}21 </table>22 );23});
Типичные ошибки при оптимизации
▸Отсутствие зависимостей в массивах
Самая частая ошибка — пустой массив зависимостей, когда зависимости реально есть. Это приводит к багам с устаревшими замыканиями.
▸Излишняя мемоизация
Не мемоизируйте всё подряд. Каждый useMemo и useCallback добавляет накладные расходы на сравнение зависимостей. Используйте Profiler для измерения реального эффекта.
▸Мемоизация нестабильных ссылок
Если вы мемоизируете функцию, но передаете нестабильный объект в пропсы — мемоизация не сработает. Убедитесь, что все зависимости стабильны.
Заключение
React.memo, useMemo и useCallback — мощные инструменты оптимизации. Используйте их осознанно: профилируйте, выявляйте реальные проблемы и применяйте мемоизацию точечно. Помните: premature optimization is the root of all evil. Начинайте с простого кода и оптимизируйте только там, где это действительно необходимо.