Ваше React-приложение работает, но со временем становится медленным: при обновлении состояния компоненты рендерятся чаще, чем нужно. Это происходит, потому что React заботится об актуальности интерфейса, но не всегда понимает, когда можно избежать лишней работы. Вот где на помощь приходит мемоизация — техника, которая позволяет оптимизировать производительность вашего приложения.
Сегодня мы разберём, как работает мемоизация, когда React-компоненты обновляются, и как использовать хуки useMemo и useCallback. На практике мы создадим компонент со счётчиком, который предотвращает ненужные вызовы функций.
Когда React-компоненты обновляются
React-компоненты обновляются, когда:
- Изменяется их состояние (state): Если вы вызываете setState, компонент будет обновлен.
- Изменяются их пропсы (props): Если родительский компонент передаёт новые пропсы, дочерний компонент рендерится заново.
- Контекст (context) обновляется: Если компонент использует useContext, он будет обновлен при изменении значения контекста.
Почему это проблема?
Обновление — это нормально, но иногда оно может быть избыточным. Например:
- Компонент отображает сложные данные, которые требуют времени на обработку.
- Функции или значения, которые передаются через пропсы, создаются заново при каждом рендере, даже если их результат остаётся неизменным.
Как мемоизация решает проблему?
Мемоизация сохраняет результаты вычислений, чтобы повторно использовать их вместо выполнения одного и того же кода. В React это достигается с помощью хуков useMemo и useCallback.
Хук useMemo
useMemo позволяет мемоизировать значение. Он пересчитывает результат только тогда, когда изменяются зависимости.
Синтаксис
const memoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
Когда использовать?
- Если вычисления требуют много ресурсов (например, фильтрация большого массива).
- Если хотите предотвратить передачу нового значения через пропсы.
Хук useCallback
useCallback мемоизирует функцию. Он возвращает ту же функцию при следующем рендере, если зависимости не изменились.
Синтаксис:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
Когда использовать?
- Если функция передаётся как пропс в дочерний компонент, чтобы избежать его перерендеривания.
- Если функция используется в зависимости другого хука (useEffect, useMemo).
Практика: Счётчик с мемоизацией
Давайте создадим приложение с двумя компонентами:
- Счётчик: Увеличивает или уменьшает число.
- Сложный компонент: Имитирует дорогостоящую операцию (например, сортировку).
Мы используем мемоизацию, чтобы предотвратить ненужные вызовы сложной функции.
Шаг 1: Создаём проект
Убедитесь, что у вас настроен React-проект. Откройте App.jsx
и добавьте код.
Шаг 2: Реализуем компонент счётчика
Синтаксис:
import React, { useState, useMemo, useCallback } from 'react';
function App() {
const [count, setCount] = useState(0);
// Увеличение и уменьшение счётчика
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
// Дорогостоящая вычислительная функция
const computeExpensiveValue = (count) => {
console.log('Выполняются сложные вычисления...');
return count * 2;
};
// Мемоизация результата
const memoizedValue = useMemo(() => computeExpensiveValue(count), [count]);
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Счётчик с мемоизацией</h1>
<p>Текущее значение: {count}</p>
<p>Сложное значение: {memoizedValue}</p>
<button onClick={increment} style={buttonStyle}>Увеличить</button>
<button onClick={decrement} style={buttonStyle}>Уменьшить</button>
</div>
);
}
const buttonStyle = {
margin: '5px',
padding: '10px 20px',
fontSize: '16px',
backgroundColor: '#007BFF',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
};
export default App;
Что здесь происходит?
Функция computeExpensiveValue:
- Имитирует дорогую операцию (например, вычисления или обработку данных).
- Вызывается при каждом обновлении состояния, если не использовать мемоизацию.
useMemo:
- Сохраняет результат функции computeExpensiveValue, пока count не изменится.
- Теперь дорогостоящая операция вызывается только при изменении count.
Шаг 3: Добавляем дочерний компонент с useCallback
Добавим компонент, который принимает функцию из App через пропсы. Мы используем useCallback, чтобы избежать ненужного рендеринга.
function Child({ onButtonClick }) {
console.log('Дочерний компонент рендерится');
return (
<button onClick={onButtonClick} style={childButtonStyle}>
Выполнить действие
</button>
);
}
const childButtonStyle = {
margin: '10px',
padding: '10px 20px',
fontSize: '16px',
backgroundColor: '#28a745',
color: '#fff',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
};
В App подключим дочерний компонент:
function App() {
const [count, setCount] = useState(0);
const increment = () => setCount((prev) => prev + 1);
const decrement = () => setCount((prev) => prev - 1);
const computeExpensiveValue = (count) => {
console.log('Выполняются сложные вычисления...');
return count * 2;
};
const memoizedValue = useMemo(() => computeExpensiveValue(count), [count]);
// Меморизируем функцию для предотвращения лишнего рендеринга
const handleAction = useCallback(() => {
console.log('Действие выполнено!');
}, []);
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Счётчик с мемоизацией</h1>
<p>Текущее значение: {count}</p>
<p>Сложное значение: {memoizedValue}</p>
<button onClick={increment} style={buttonStyle}>Увеличить</button>
<button onClick={decrement} style={buttonStyle}>Уменьшить</button>
<Child onButtonClick={handleAction} />
</div>
);
}
Шаг 4: Запуск проекта
Запустите проект:
npm run dev
Откройте приложение. При изменении счётчика консоль показывает, что сложные вычисления выполняются только при изменении count. Дочерний компонент рендерится только при необходимости.
Итоги
Сегодня мы узнали:
- Когда React-компоненты перерендериваются.
- Как использовать useMemo для мемоизации вычислений.
- Как использовать useCallback для предотвращения создания новых функций.
На практике мы создали оптимизированный компонент со счётчиком, где мемоизация предотвращает ненужные вызовы функций.
Когда использовать мемоизацию?
- Если ваш компонент вызывает сложные вычисления.
- Если дочерние компоненты рендерятся из-за изменений пропсов.
Но помните: мемоизация — не панацея. Используйте её только там, где это действительно необходимо, иначе вы усложните код без пользы.
Комментарии
0