Как реализовать useState для сохранения состояния между вызовами функции?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимизация перерендера: проверка изменений пропсов
Один из главных врагов производительности React приложений — ненужные перерендеры. Когда компонент перерендеривается несмотря на то, что его пропсы не изменились, это пустая трата ресурсов. Есть несколько способов отловить и предотвратить такие случаи.
Способ 1: React.memo для функциональных компонентов
React.memo оборачивает компонент и пропускает ре-рендер, если пропсы не изменились (поверхностное сравнение).
// Без оптимизации
function Button({ label, onClick }) {
console.log('Button rendered');
return <button onClick={onClick}>{label}</button>;
}
// С оптимизацией
const MemoButton = React.memo(Button);
// В родительском компоненте
function Parent() {
const [count, setCount] = useState(0);
// handleClick создается заново при каждом ре-рендере
const handleClick = () => console.log('clicked');
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
<MemoButton label="Клик" onClick={handleClick} />
</>
);
}
// ПРОБЛЕМА: handleClick всегда новая функция, поэтому MemoButton все равно перерендеривается!
Способ 2: useCallback для стабилизации функций
useCallback мемоизирует функцию и возвращает ту же ссылку, если зависимости не изменились.
function Parent() {
const [count, setCount] = useState(0);
// handleClick будет одной и той же функцией, пока нет зависимостей
const handleClick = useCallback(() => {
console.log('clicked');
}, []); // Пустой массив = никогда не пересоздавать
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
<MemoButton label="Клик" onClick={handleClick} />
{/* Теперь MemoButton не перерендеривается при увеличении count */}
</>
);
}
Способ 3: Пользовательское сравнение пропсов
Если поверхностного сравнения недостаточно, можно написать собственную функцию сравнения:
// Компонент, который часто получает новые объекты в пропсах
function UserCard({ user, theme }) {
return (
<div style={theme}>
<h2>{user.name}</h2>
</div>
);
}
// Вместо дефолтного сравнения используем собственное
const MemoUserCard = React.memo(
UserCard,
(prevProps, nextProps) => {
// Возвращаем true если пропсы ОДИНАКОВЫЕ (пропускаем ре-рендер)
// Возвращаем false если пропсы РАЗНЫЕ (выполняем ре-рендер)
return (
prevProps.user.id === nextProps.user.id &&
prevProps.theme.color === nextProps.theme.color
);
}
);
// Использование
function App() {
const [count, setCount] = useState(0);
const theme = { color: 'blue' }; // Создается заново каждый раз!
return (
<>
<button onClick={() => setCount(count + 1)}>Счетчик: {count}</button>
<MemoUserCard user={{ id: 1, name: 'John' }} theme={theme} />
{/* Будет перерендеривается каждый раз, потому что theme это новый объект */}
</>
);
}
Способ 4: useMemo для сложных объектов в пропсах
Если пропс это объект, который пересчитывается каждый раз, используй useMemo:
function Parent({ userId }) {
const [count, setCount] = useState(0);
// ПЛОХО: user создается заново при каждом ре-рендере
// const user = { id: userId, name: 'John' };
// ХОРОШО: user мемоизируется, создается только если userId изменился
const user = useMemo(() => {
return { id: userId, name: 'John' };
}, [userId]);
return (
<>
<button onClick={() => setCount(count + 1)}>Счетчик: {count}</button>
<MemoUserCard user={user} />
{/* Теперь MemoUserCard не перерендеривается при изменении count */}
</>
);
}
Способ 5: Проверка с помощью React DevTools Profiler
DevTools Profiler показывает, почему компонент перерендеривается:
// 1. Открой React DevTools
// 2. Перейди в Profiler
// 3. Нажми на кнопку "Record"
// 4. Выполни действие (например, кликни кнопку)
// 5. Посмотри, какие компоненты перерендеривались
function DebugComponent({ data }) {
console.log('DebugComponent rendered');
return <div>{data.value}</div>;
}
// В DevTools увидишь: "Props changed: data"
// Это значит, что props.data изменился или это новый объект
Способ 6: Полный пример оптимизации
// Мемоизированный компонент с собственным сравнением
const OptimizedListItem = React.memo(
({ item, onSelect }) => {
console.log(`ListItem ${item.id} rendered`);
return (
<div onClick={() => onSelect(item.id)}>
{item.name} - ${item.price}
</div>
);
},
(prevProps, nextProps) => {
// Сравниваем только нужные поля
return (
prevProps.item.id === nextProps.item.id &&
prevProps.item.name === nextProps.item.name &&
prevProps.item.price === nextProps.item.price
// onSelect не сравниваем, т.к. она должна обновляться
);
}
);
function ProductList({ products }) {
const [selectedId, setSelectedId] = useState(null);
const [sortBy, setSortBy] = useState('name');
// Стабильная функция
const handleSelect = useCallback((id) => {
setSelectedId(id);
}, []);
// Мемоизированный список (только если products изменился)
const sortedProducts = useMemo(() => {
return [...products].sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
return a.price - b.price;
});
}, [products, sortBy]);
return (
<>
<button onClick={() => setSortBy('price')}>Сортировать по цене</button>
{sortedProducts.map((product) => (
<OptimizedListItem
key={product.id}
item={product}
onSelect={handleSelect}
/>
))}
</>
);
}
Когда использовать оптимизацию
// Используй React.memo, useMemo, useCallback когда:
// 1. Компонент часто перерендеривается без причины
// 2. Рендеринг дорогой (много вычислений или DOM элементов)
// 3. Пропсы часто одинаковые (но это новые объекты/функции)
// Не переусложняй без необходимости!
// Сначала профилируй, потом оптимизируй
Шпаргалка
| Инструмент | Для чего | Когда использовать |
|---|---|---|
| React.memo | Пропускает ре-рендер если пропсы не изменились | Функциональные компоненты |
| useCallback | Стабилизирует функцию | Функции передаются в props |
| useMemo | Кэширует результат вычисления | Дорогие вычисления, объекты/массивы |
| Кастомное сравнение | Глубокое сравнение пропсов | Сложные структуры данных |
Правильная оптимизация пропсов и ре-рендеров — это баланс между производительностью и сложностью кода. Всегда сначала профилируй, чтобы убедиться, что оптимизация действительно нужна.