Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Мемоизация в React: useMemo и useCallback
Мемоизация — это техника кэширования результатов функции, чтобы избежать повторных расчётов при одинаковых входных параметрах. В React есть два специальных хука для мемоизации: useMemo и useCallback. Они помогают оптимизировать производительность путём предотвращения ненужных пересчётов и ре-рендеров.
useMemo: кэширование значений
useMemo мемоизирует вычисленное значение и пересчитывает его только когда зависимости изменились.
Синтаксис
const memoizedValue = useMemo(() => {
// Дорогостоящее вычисление
return computeExpensiveValue(a, b);
}, [a, b]); // Зависимости
useMemo принимает две аргумента:
- Функция, которая возвращает значение для мемоизации
- Массив зависимостей — пересчет происходит только если они изменились
Практический пример
import { useMemo } from 'react';
// Функция для фильтрации и сортировки большого списка
const filterAndSortUsers = (users, searchTerm) => {
console.log('Фильтруем и сортируем...');
return users
.filter(user => user.name.includes(searchTerm))
.sort((a, b) => a.name.localeCompare(b.name));
};
export function UserList({ users, searchTerm }) {
// Плохо: фильтрация происходит при каждом ре-рендере
const filteredUsers = filterAndSortUsers(users, searchTerm);
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Хорошо: фильтрация кэшируется
export function OptimizedUserList({ users, searchTerm }) {
const filteredUsers = useMemo(() => {
console.log('Фильтруем (только если нужно)');
return filterAndSortUsers(users, searchTerm);
}, [users, searchTerm]); // Пересчитываем только если users или searchTerm изменились
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Сложный пример
export function DataAnalysis({ data }) {
// Дорогостоящий расчёт
const statistics = useMemo(() => {
console.time('Calculating statistics');
const sum = data.reduce((acc, val) => acc + val, 0);
const avg = sum / data.length;
const max = Math.max(...data);
const min = Math.min(...data);
const median = data.sort((a, b) => a - b)[Math.floor(data.length / 2)];
console.timeEnd('Calculating statistics');
return { sum, avg, max, min, median };
}, [data]); // Пересчитываем только если data изменился
return (
<div>
<p>Сумма: {statistics.sum}</p>
<p>Средняя: {statistics.avg}</p>
<p>Макс: {statistics.max}</p>
<p>Мин: {statistics.min}</p>
<p>Медиана: {statistics.median}</p>
</div>
);
}
useCallback: кэширование функций
useCallback мемоизирует функцию, чтобы при ре-рендере передавать ту же ссылку на функцию дочерним компонентам.
Синтаксис
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]); // Зависимости
Проблема без useCallback
import { useState } from 'react';
// Дочерний компонент
const Button = ({ onClick }) => {
console.log('Button re-rendered');
return <button onClick={onClick}>Click me</button>;
};
// Родительский компонент
export function Parent() {
const [count, setCount] = useState(0);
// Плохо: новая функция создаётся при каждом ре-рендере
const handleClick = () => {
setCount(count + 1);
};
console.log('Parent re-rendered');
return (
<div>
<p>Count: {count}</p>
{/* Button ре-рендерится каждый раз, потому что handleClick — новая функция */}
<Button onClick={handleClick} />
</div>
);
}
Решение с useCallback
import { useState, useCallback } from 'react';
// Оптимизированный дочерний компонент с React.memo
const Button = React.memo(({ onClick }) => {
console.log('Button re-rendered');
return <button onClick={onClick}>Click me</button>;
});
export function OptimizedParent() {
const [count, setCount] = useState(0);
// Хорошо: функция кэшируется и не создаётся заново каждый раз
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]); // Зависимость от count
console.log('Parent re-rendered');
return (
<div>
<p>Count: {count}</p>
{/* Button НЕ ре-рендерится, если handleClick не изменилась */}
<Button onClick={handleClick} />
</div>
);
}
Практический пример с формой
import { useCallback } from 'react';
const SearchBox = React.memo(({ onSearch }) => {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
onSearch(e.target.value); // Вызываем callback
};
return <input onChange={handleChange} placeholder="Поиск..." />;
});
export function SearchResults({ items }) {
const [searchTerm, setSearchTerm] = useState('');
// Мемоизируем callback, чтобы SearchBox не ре-рендерился
const handleSearch = useCallback((term) => {
setSearchTerm(term);
}, []);
const filtered = items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<>
<SearchBox onSearch={handleSearch} />
<ul>
{filtered.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</>
);
}
Сравнение useMemo и useCallback
// useMemo — кэширует вычисленное значение
const value = useMemo(() => {
return expensiveCalculation(a, b);
}, [a, b]);
// useCallback — кэширует функцию (эквивалентно useMemo)
const callback = useCallback(() => {
return expensiveCalculation(a, b);
}, [a, b]);
// Это то же самое:
const callback = useMemo(() => {
return () => expensiveCalculation(a, b);
}, [a, b]);
Когда использовать мемоизацию
Используй useMemo когда:
- Вычисление дорогостоящее (фильтрация больших списков, матем расчёты)
- Результат передаётся в дочерние компоненты (memo компоненты)
- Вычисление вызывает побочные эффекты в других местах
const expensiveValue = useMemo(() => {
// Вычисление занимает много времени
return heavyCalculation(items);
}, [items]);
Используй useCallback когда:
- Callback передаётся в оптимизированный дочерний компонент (React.memo)
- Callback — зависимость другого хука (useEffect, другие хуки)
- Функция используется как ключ в карте
const handleClick = useCallback(() => {
doSomething();
}, [dependency]);
// Использование с React.memo
const Child = React.memo(({ onClick }) => {
return <button onClick={onClick}>Click</button>;
});
return <Child onClick={handleClick} />;
Частые ошибки
Ошибка 1: неправильные зависимости
// Плохо: зависимость пропущена
const value = useMemo(() => {
return calculate(a, b); // Использует b
}, [a]); // b пропущена!
// Хорошо
const value = useMemo(() => {
return calculate(a, b);
}, [a, b]);
Ошибка 2: чрезмерная мемоизация
// Плохо: мемоизация простого вычисления замедляет код
const double = useMemo(() => count * 2, [count]);
// Хорошо: просто вычисли
const double = count * 2;
Мемоизация — мощный инструмент для оптимизации, но используй её мудро. Основное правило: сначала убедись что есть проблема производительности, потом добавляй мемоизацию.