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

Как реализовать Pure Component при функциональном подходе?

1.8 Middle🔥 131 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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
  • Не переусложняй — используй только для дорогих компонентов