← Назад к вопросам

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 разумно — только для больших компонентов
Memo проверяет равенство props по значению или по референсу | PrepBro