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

Как проверить, нужно ли обновлять компонент в зависимости от изменения пропсов?

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

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

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

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

Оптимизация рендеринга при изменении props

В React есть несколько способов проверить, нужно ли обновлять компонент при изменении props.

1. React.memo (рекомендуется)

// ПЛОХО: Компонент рендерится при каждом рендере родителя
export function UserCard({ user, onDelete }) {
  console.log('UserCard rendered');
  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={onDelete}>Delete</button>
    </div>
  );
}

// ХОРОШО: Пропускает рендер если props не изменились
export const UserCard = React.memo(function({ user, onDelete }) {
  console.log('UserCard rendered');
  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={onDelete}>Delete</button>
    </div>
  );
});

// Теперь рендер пропускается если user и onDelete не изменились

2. Кастомное сравнение props

// Если нужна кастомная логика сравнения
export const UserCard = React.memo(
  function({ user, onDelete }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // Возвращаем true если props равны (пропускаем рендер)
    // Возвращаем false если props разные (выполняем рендер)
    return (
      prevProps.user.id === nextProps.user.id &&
      prevProps.onDelete === nextProps.onDelete
    );
  }
);

// Пример: сравниваем только id, игнорируя другие поля user
export const UserCard = React.memo(
  function({ user, onDelete }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // Пропускаем рендер только если id одинаковый
    return prevProps.user.id === nextProps.user.id;
  }
);

3. useMemo для сложных вычислений

import { useMemo } from 'react';

export function Parent({ users }) {
  // usersList пересчитывается только если users изменился
  const usersList = useMemo(() => {
    return users.map(u => ({
      ...u,
      displayName: `${u.firstName} ${u.lastName}`
    }));
  }, [users]);

  return <UserList users={usersList} />;
}

// Без useMemo usersList был бы новый объект на каждый рендер
// С useMemo он остаётся тем же если users не изменился

4. useCallback для функций

import { useCallback } from 'react';

export function Parent() {
  // handleDelete переиспользуется если нет зависимостей
  const handleDelete = useCallback((id) => {
    console.log('Delete:', id);
  }, []);  // Пустой массив = функция не пересоздаётся

  return <UserCard onDelete={handleDelete} />;
}

// Без useCallback новая функция на каждый рендер
// Это вызовет пересчет UserCard даже если логика одна и та же

5. Сравнение props в классовом компоненте

// В классовых компонентах используй shouldComponentUpdate
class UserCard extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Возвращаем true если нужен рендер, false если пропускаем
    return (
      this.props.user.id !== nextProps.user.id ||
      this.props.count !== nextProps.count
    );
  }

  render() {
    return <div>{this.props.user.name}</div>;
  }
}

// Или используй PureComponent (shallow compare props и state)
class UserCard extends React.PureComponent {
  // Автоматически сравнивает props и state
  render() {
    return <div>{this.props.user.name}</div>;
  }
}

6. Практический пример с useCallback

import { useState, useCallback } from 'react';

export function UserList() {
  const [users, setUsers] = useState([]);

  // ПЛОХО: Новая функция на каждый рендер
  const handleDelete = (id) => {
    setUsers(users.filter(u => u.id !== id));
  };

  // ХОРОШО: Функция переиспользуется
  const handleDelete = useCallback((id) => {
    setUsers(prev => prev.filter(u => u.id !== id));
  }, []);

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onDelete={handleDelete}  // Теперь функция стабильна
        />
      ))}
    </div>
  );
}

// UserCard.tsx
export const UserCard = React.memo(function({ user, onDelete }) {
  console.log('Rendered:', user.id);
  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={() => onDelete(user.id)}>Delete</button>
    </div>
  );
});

7. Проверка с помощью зависимостей

import { useEffect } from 'react';

export function UserCard({ user, onDelete }) {
  // Отслеживаем когда user изменился
  useEffect(() => {
    console.log('User changed:', user.id);
  }, [user]);  // Зависимость user

  // Отслеживаем когда ID изменился
  useEffect(() => {
    console.log('User ID changed:', user.id);
  }, [user.id]);  // Зависимость только от ID

  return <div>{user.name}</div>;
}

8. Библиотека для глубокого сравнения

import { isEqual } from 'lodash';

// Кастомное сравнение со скрытыми полями
export const UserCard = React.memo(
  function({ user, onDelete }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // isEqual глубоко сравнивает объекты
    return isEqual(prevProps.user, nextProps.user);
  }
);

// ВНИМАНИЕ: глубокое сравнение медленнее, чем поверхностное
// Используй только если необходимо

Лучшие практики

// 1. ИСПОЛЬЗУЙ REACT.MEMO для простых компонентов
export const Button = React.memo(function({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>;
});

// 2. ИСПОЛЬЗУЙ USECALLBACK для функций
const handleClick = useCallback(() => {
  console.log('Clicked');
}, []);

// 3. ИСПОЛЬЗУЙ USEMEMO для дорогих вычислений
const filtered = useMemo(() => {
  return items.filter(x => x.value > 100);
}, [items]);

// 4. ПРАВИЛЬНЫЕ ЗАВИСИМОСТИ в useEffect
useEffect(() => {
  // код
}, [dependency1, dependency2]);

// 5. ИЗБЕГАЙ СОЗДАНИЯ НОВЫХ ОБЪЕКТОВ В PROPS
// ПЛОХО:
<UserCard user={{ id: 1, name: 'John' }} />
// Новый объект на каждый рендер!

// ХОРОШО:
const user = useMemo(() => ({ id: 1, name: 'John' }), []);
<UserCard user={user} />

Ответ: используй React.memo для автоматического сравнения props, useCallback для стабильных функций, и useMemo для дорогих вычислений. Это позволяет React оптимизировать рендеринг.