Как реализовать Pure Component при функциональном подходе?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Pure Component в функциональном подходе React
PureComponent — это классовый компонент, который автоматически сравнивает props и state и перерендеривается только если они изменились. В функциональном подходе это достигается через React.memo.
История: Класс vs Функция
Класс PureComponent (старый подход)
import React from 'react';
// Классовый компонент
class UserCard extends React.PureComponent {
render() {
const { name, age } = this.props;
console.log('Рендер UserCard');
return (
<div>
<p>{name}</p>
<p>{age}</p>
</div>
);
}
}
export default UserCard;
PureComponent автоматически сравнивает props и state через shallow comparison (поверхностное сравнение).
Функциональный компонент с memo (новый подход)
import React from 'react';
// Функциональный компонент
function UserCard({ name, age }) {
console.log('Рендер UserCard');
return (
<div>
<p>{name}</p>
<p>{age}</p>
</div>
);
}
// Оборачиваем в memo для того же эффекта
export default React.memo(UserCard);
Теперь компонент не будет перерендеривься, если props остались теми же.
React.memo: базовое использование
function Product({ id, name, price }) {
console.log('Рендер Product');
return (
<div>
<h3>{name}</h3>
<p>${price}</p>
</div>
);
}
// Компонент перерендерится только если id, name или price изменились
export default React.memo(Product);
Проблема: объекты и функции как props
memo использует shallow comparison, поэтому если передаёшь объекты или функции, каждый раз они новые:
function Parent() {
// ПРОБЛЕМА: новый объект создаётся каждый раз
const user = { name: 'Иван', age: 28 };
// ПРОБЛЕМА: новая функция создаётся каждый раз
const handleClick = () => console.log('Клик');
return (
<>
<UserCard user={user} />
<Button onClick={handleClick} />
</>
);
}
function UserCard({ user }) {
console.log('Рендер UserCard'); // БУДЕТ РЕНДЕРИТЬСЯ КАЖДЫЙ РАЗ!
return <div>{user.name}</div>;
}
const MemoUserCard = React.memo(UserCard);
Решение 1: Custom comparison функция
React.memo принимает второй аргумент — кастомную функцию сравнения:
function UserCard({ user }) {
return <div>{user.name}</div>;
}
// Кастомная функция сравнения
function compareProps(prevProps, nextProps) {
// Возвращаем true если props ОДИНАКОВЫЕ (не перерендеривать)
// Возвращаем false если props РАЗНЫЕ (перерендеривать)
return prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name;
}
export default React.memo(UserCard, compareProps);
Решение 2: useMemo для объектов
Используй useMemo в родительском компоненте, чтобы не пересоздавать объекты:
import { useMemo } from 'react';
function Parent({ userId }) {
// user будет одним и тем же объектом, пока userId не изменится
const user = useMemo(() => {
return { id: userId, name: 'Иван', age: 28 };
}, [userId]);
return <MemoUserCard user={user} />;
}
function UserCard({ user }) {
console.log('Рендер UserCard'); // Рендерится только когда меняется userId
return <div>{user.name}</div>;
}
const MemoUserCard = React.memo(UserCard);
Решение 3: useCallback для функций
Используй useCallback для стабильных функций:
import { useCallback } from 'react';
function Parent({ userId }) {
// handleClick будет одной и той же функцией, пока userId не изменится
const handleClick = useCallback(() => {
console.log('Клик от пользователя', userId);
}, [userId]);
return <MemoButton onClick={handleClick} />;
}
function Button({ onClick }) {
console.log('Рендер Button');
return <button onClick={onClick}>Нажми</button>;
}
const MemoButton = React.memo(Button);
Полный пример: оптимизированный список
import React, { useState, useMemo, useCallback } from 'react';
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: 'Иван' },
{ id: 2, name: 'Петр' },
{ id: 3, name: 'Мария' }
]);
const [selectedId, setSelectedId] = useState(null);
// Стабилизируем функцию через useCallback
const handleSelect = useCallback((id) => {
setSelectedId(id);
}, []);
return (
<div>
{users.map(user => (
<MemoUserItem
key={user.id}
user={user}
isSelected={selectedId === user.id}
onSelect={handleSelect}
/>
))}
</div>
);
}
function UserItem({ user, isSelected, onSelect }) {
console.log('Рендер UserItem', user.id);
return (
<div
onClick={() => onSelect(user.id)}
style={{
padding: '10px',
background: isSelected ? 'lightblue' : 'white'
}}
>
{user.name}
</div>
);
}
const MemoUserItem = React.memo(UserItem);
Сравнение: Shallow vs Deep comparison
const a = { name: 'Иван' };
const b = { name: 'Иван' };
// Shallow comparison: проверяет только ссылку
a === b; // false (разные объекты в памяти)
// Deep comparison: проверяет содержимое
JSON.stringify(a) === JSON.stringify(b); // true
PureComponent и memo используют shallow comparison:
function Parent() {
// Каждый раз новый объект
const user1 = { name: 'Иван' };
const user2 = { name: 'Иван' };
// user1 === user2 // false!
// Поэтому компонент перерендерится каждый раз
return <MemoCard user={user1} />;
}
Когда NOT использовать memo
// ПЛОХО: не нужен memo если компонент всегда рендерится
const Button = React.memo(({ label }) => {
return <button>{label}</button>;
});
// Если родитель всегда меняется
function Parent() {
return <Button label={Math.random()} />; // label ВСЕГДА новый
}
// ХОРОШО: используй memo только для дорогих компонентов
const ExpensiveList = React.memo(({ items }) => {
// Много вычислений
const processed = items.map(/* ... */); // долго
return <div>{/* ... */}</div>;
});
Best practice: комбинация
import { memo, useCallback, useMemo } from 'react';
const Card = memo(function Card({ user, onClick }) {
return (
<div onClick={onClick}>
{user.name}
</div>
);
});
function App() {
const [filter, setFilter] = useState('');
const [users, setUsers] = useState([/* ... */]);
// Стабилизируем коллбэк
const handleCardClick = useCallback((userId) => {
console.log('Выбран', userId);
}, []);
// Стабилизируем отфильтрованный список
const filteredUsers = useMemo(() => {
return users.filter(u => u.name.includes(filter));
}, [users, filter]);
return (
<div>
{filteredUsers.map(user => (
<Card
key={user.id}
user={user}
onClick={() => handleCardClick(user.id)}
/>
))}
</div>
);
}
Итог
React.memo — это функциональный эквивалент PureComponent:
- Предотвращает ненужные перерендеры если props не изменились
- Использует shallow comparison (как и PureComponent)
- Для объектов и функций нужны useMemo и useCallback
- Не переусложняй — используй только для дорогих компонентов