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

Как реализовать useState для сохранения состояния между вызовами функции?

1.8 Middle🔥 221 комментариев
#React

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

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

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

Оптимизация перерендера: проверка изменений пропсов

Один из главных врагов производительности 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Кэширует результат вычисленияДорогие вычисления, объекты/массивы
Кастомное сравнениеГлубокое сравнение пропсовСложные структуры данных

Правильная оптимизация пропсов и ре-рендеров — это баланс между производительностью и сложностью кода. Всегда сначала профилируй, чтобы убедиться, что оптимизация действительно нужна.