← Назад к вопросам
Memo проверяет равенство props по значению или по референсу
2.0 Middle🔥 191 комментариев
#React#Оптимизация и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
React.memo: проверка props по референсу
React.memo проверяет равенство props по референсу (по ссылке на объект в памяти), а не по значению. Это критически важное различие, которое часто приводит к ошибкам.
Основное поведение
// ❌ Обычный компонент без мемоизации
function UserCard({ user }) {
console.log("UserCard rendered"); // Печатается при каждом рендере
return <div>{user.name}</div>;
}
// ✅ С React.memo
const MemoizedUserCard = React.memo(UserCard);
function App() {
const user = { name: "John" };
return <MemoizedUserCard user={user} />;
}
// На каждый рендер App создаётся НОВЫЙ объект user
// React.memo видит разные референсы — перерендер всё равно происходит!
Проверка по референсу, а не по значению
// Разные объекты со одинаковыми значениями
const obj1 = { name: "John", age: 30 };
const obj2 = { name: "John", age: 30 };
obj1 === obj2; // false — разные объекты в памяти!
obj1.name === obj2.name; // true — одинаковые значения
// React.memo ошибочно считает это разными props
const MemoCard = React.memo(({ user }) => <div>{user.name}</div>);
// Это вызовет перерендер, даже если user с одинаковым содержимым
<MemoCard user={{ name: "John", age: 30 }} />
<MemoCard user={{ name: "John", age: 30 }} /> // Перерендер!
Проблема с инлайн объектами
// ❌ Плохо: создаём новый объект на каждый рендер
function Parent() {
return (
<MemoizedChild user={{ name: "John" }} />
);
}
// ✅ Хорошо: переиспользуем один объект
const defaultUser = { name: "John" };
function Parent() {
return (
<MemoizedChild user={defaultUser} />
);
}
// ✅ Или используем useMemo
function Parent() {
const user = useMemo(() => ({ name: "John" }), []);
return (
<MemoizedChild user={user} />
);
}
Проблема с инлайн функциями
// ❌ Плохо: новая функция создаётся на каждый рендер
function Parent() {
return (
<MemoizedButton
onClick={() => console.log("clicked")}
/>
);
}
// ✅ Хорошо: используем useCallback
function Parent() {
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return (
<MemoizedButton onClick={handleClick} />
);
}
Полный пример проблемы
// MemoizedUserCard не будет перерендериться,
// если props (по референсу) не изменились
const UserCard = React.memo(({ user }) => {
console.log("UserCard render");
return <div>{user.name} - {user.age}</div>;
});
function App() {
const [count, setCount] = useState(0);
// ❌ На каждый рендер App создаётся новый объект user
const user = { name: "John", age: 30 };
return (
<>
<button onClick={() => setCount(count + 1)}>
Increment: {count}
</button>
<UserCard user={user} /> {/* Перерендер каждый раз! */}
</>
);
}
// Вывод консоли:
// UserCard render (первый рендер)
// UserCard render (при клике на button — НЕОЖИДАННО!)
// UserCard render (при втором клике)
Решение: useMemo для объектов
const UserCard = React.memo(({ user }) => {
console.log("UserCard render");
return <div>{user.name} - {user.age}</div>;
});
function App() {
const [count, setCount] = useState(0);
// ✅ Объект переиспользуется между рендерами
const user = useMemo(() => ({ name: "John", age: 30 }), []);
return (
<>
<button onClick={() => setCount(count + 1)}>
Increment: {count}
</button>
<UserCard user={user} /> {/* НЕ перерендер, пока user в зависимостях не изменится */}
</>
);
}
// Вывод консоли:
// UserCard render (первый рендер)
// (при кликах на button — нет перерендера UserCard!)
Когда объект действительно изменился
const UserCard = React.memo(({ user }) => {
console.log("UserCard render");
return <div>{user.name} - {user.age}</div>;
});
function App() {
const [name, setName] = useState("John");
// ✅ Объект зависит от name
const user = useMemo(() => ({ name, age: 30 }), [name]);
return (
<>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<UserCard user={user} /> {/* Перерендер только если name изменилась */}
</>
);
}
// Вывод консоли:
// UserCard render (первый рендер)
// UserCard render (при изменении name)
Пользовательская функция сравнения
Если хочешь, чтобы React.memo проверял равенство по значению, можно передать кастомную функцию:
// По умолчанию: shallow сравнение (по референсу)
const MemoComponent = React.memo(Component);
// Кастомное сравнение: проверяем содержимое
const MemoComponent = React.memo(Component, (prevProps, nextProps) => {
// Возвращаем true если props ОДИНАКОВЫЕ (не перерендер)
// Возвращаем false если props РАЗНЫЕ (перерендер)
return prevProps.user.name === nextProps.user.name &&
prevProps.user.age === nextProps.user.age;
});
// Использование
function App() {
const [count, setCount] = useState(0);
const user = { name: "John", age: 30 }; // Новый объект каждый раз
return (
<>
<button onClick={() => setCount(count + 1)}>Increment</button>
<MemoComponent user={user} /> {/* НЕ перерендер, если name и age одинаковые */}
</>
);
}
Глубокое сравнение (опасно!)
// ❌ Плохо: глубокое сравнение — медленно
const MemoComponent = React.memo(Component, (prevProps, nextProps) => {
return JSON.stringify(prevProps) === JSON.stringify(nextProps);
});
// Это работает, но очень медленно на больших объектах!
// Используй только для небольших объектов.
Практический пример: список с мемоизацией
// Компонент списка
const UserList = React.memo(({ users, onSelectUser }) => {
console.log("UserList render");
return (
<ul>
{users.map(user => (
<UserItem
key={user.id}
user={user}
onSelect={onSelectUser}
/>
))}
</ul>
);
});
// Компонент элемента списка
const UserItem = React.memo(({ user, onSelect }) => {
console.log(`UserItem ${user.id} render`);
return (
<li onClick={() => onSelect(user.id)}>
{user.name}
</li>
);
});
function App() {
const [selectedId, setSelectedId] = useState(null);
// ✅ Правильно: users не создаётся каждый рендер
const users = useMemo(() => [
{ id: 1, name: "John" },
{ id: 2, name: "Jane" },
], []);
// ✅ Правильно: функция мемоизирована
const handleSelectUser = useCallback((id) => {
setSelectedId(id);
}, []);
return (
<UserList
users={users}
onSelectUser={handleSelectUser}
/>
);
}
// Вывод консоли:
// UserList render (первый рендер)
// UserItem 1 render (первый рендер)
// UserItem 2 render (первый рендер)
// (при клике на элемент — нет перерендера UserList и других элементов!)
Частая ошибка: забыли зависимости
// ❌ Неправильно: зависимости не указаны
const user = useMemo(() => ({ name, age: 30 })); // Без зависимостей!
// Объект создаётся один раз и никогда не обновляется
// Если name изменится, компонент всё равно будет видеть старое значение
// ✅ Правильно: указываем зависимости
const user = useMemo(() => ({ name, age: 30 }), [name]);
Когда стоит использовать React.memo?
Используй React.memo, если:
- Компонент дорогостоящий (долгий рендер)
- Компонент часто получает те же props
- Родитель часто перерендерится
НЕ используй React.memo, если:
- Компонент простой и быстро рендерится
- Props всегда разные
- Это микро-оптимизация
// ❌ Неправильно: излишняя мемоизация
const Button = React.memo(({ onClick, children }) => {
return <button onClick={onClick}>{children}</button>;
});
// Мемоизация медленнее, чем сам рендер!
// ✅ Правильно: только для дорогостоящих компонентов
const HeavyChart = React.memo(({ data, config }) => {
// Сложные вычисления, большой SVG и т.д.
return <ComplexChart {...} />;
});
Заключение
- React.memo проверяет props по референсу, не по значению
- Новые объекты и функции на каждый рендер вызывают перерендер
- Используй useMemo и useCallback для стабилизации props
- Кастомное сравнение позволяет проверять по значению, но медленнее
- Используй React.memo разумно — только для больших компонентов