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

Как избежать ререндер компонента?

1.7 Middle🔥 201 комментариев
#React#State Management#Оптимизация и производительность

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

Оптимизация: как избежать лишних перерендеров

Ненужные перерендеры замедляют приложение и расходуют ресурсы. React предоставляет несколько инструментов для их предотвращения.

1. React.memo для функциональных компонентов

React.memo оборачивает компонент и предотвращает его перерендер, если пропсы не изменились:

const Child = React.memo(function Child({ name }) {
  console.log('Child отрендерился');
  return <div>Привет, {name}!</div>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [name] = useState('Иван');

  return (
    <>
      <p>Счётчик: {count}</p>
      <button onClick={() => setCount(count + 1)}>Увеличить</button>
      {/* Child не перерендерится, т.к. name не изменился */}
      <Child name={name} />
    </>
  );
}

Без React.memo компонент Child перерендерился бы каждый раз при клике (когда меняется count).

2. useMemo для дорогих вычислений

useMemo запоминает результат вычисления и пересчитывает только при изменении зависимостей:

function ExpensiveComponent({ items }) {
  // Вычисление выполняется один раз при первом рендере
  // и повторяется только если items изменится
  const sortedItems = useMemo(() => {
    console.log('Сортировка...');
    return items.sort((a, b) => a - b);
  }, [items]);

  return <div>{sortedItems.join(', ')}</div>;
}

Важно: используй useMemo только для действительно дорогих вычислений!

3. useCallback для стабильных функций

useCallback запоминает функцию, чтобы она не пересоздавалась каждый раз:

function Parent() {
  const [count, setCount] = useState(0);

  // Без useCallback эта функция создавалась бы заново при каждом рендере
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // пустой массив зависимостей — функция никогда не меняется

  return <Child onAction={handleClick} />;
}

const Child = React.memo(({ onAction }) => {
  // Без useCallback в родителе Child перерендерился бы каждый раз
  return <button onClick={onAction}>Клик</button>;
});

4. shouldComponentUpdate в классовых компонентах

Этот метод вручную контролирует, нужен ли перерендер:

class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Перерендер только если id изменился
    return this.props.id !== nextProps.id;
  }

  render() {
    return <div>ID: {this.props.id}</div>;
  }
}

5. PureComponent для неглубокого сравнения

Автоматически сравнивает пропсы и состояние:

class MyComponent extends React.PureComponent {
  // shouldComponentUpdate реализован с поверхностным сравнением
  render() {
    return <div>{this.props.name}</div>;
  }
}

Внимание: PureComponent сравнивает по ссылкам, не глубоко!

6. Правильная структура состояния

Делись состояние так, чтобы изменения затрагивали только нужные компоненты:

// ❌ Плохо — один большой объект состояния
const [state, setState] = useState({
  user: { name: 'Иван' },
  products: [...],
  filters: {...}
});

// ✅ Хорошо — разделённое состояние
const [user, setUser] = useState({ name: 'Иван' });
const [products, setProducts] = useState([...]);
const [filters, setFilters] = useState({...});

7. Key в списках

Правильный key предотвращает ненужные перерендеры элементов списка:

// ❌ Плохо — index как key
list.map((item, index) => <Item key={index} data={item} />)

// ✅ Хорошо — уникальный id
list.map(item => <Item key={item.id} data={item} />)

8. Кэширование селекторов (Redux/Zustand)

Используй мемоизированные селекторы в Redux:

import { createSelector } from 'reselect';

// Селектор кэшируется и не пересчитывается, если состояние не изменилось
const selectUserName = createSelector(
  state => state.user,
  user => user.name
);

9. Ленивая загрузка компонентов

Импортируй тяжёлые компоненты только когда они нужны:

const HeavyComponent = lazy(() => import('./Heavy'));

function App() {
  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

Практический пример оптимизации

// ДО — много перерендеров
function App() {
  const [users, setUsers] = useState([]);
  const [searchText, setSearchText] = useState('');

  const filteredUsers = users.filter(u => u.name.includes(searchText));

  return (
    <>
      <input value={searchText} onChange={e => setSearchText(e.target.value)} />
      <UserList users={filteredUsers} />
    </>
  );
}

// ПОСЛЕ — оптимизировано
function App() {
  const [users, setUsers] = useState([]);
  const [searchText, setSearchText] = useState('');

  const filteredUsers = useMemo(
    () => users.filter(u => u.name.includes(searchText)),
    [users, searchText]
  );

  return (
    <>
      <input value={searchText} onChange={e => setSearchText(e.target.value)} />
      <UserList users={filteredUsers} />
    </>
  );
}

const UserList = React.memo(({ users }) => {
  return <div>{users.map(u => <p key={u.id}>{u.name}</p>)}</div>;
});

Инструменты для диагностики

// React DevTools Profiler
// 1. Открой React DevTools → Profiler
// 2. Запиши сессию
// 3. Посмотри какие компоненты перерендерились

// Или используй console.log
function MyComponent() {
  console.log('Компонент отрендерился');
  return <div>...</div>;
}

Правила оптимизации

✓ Сначала измери проблему (DevTools Profiler) ✓ Используй React.memo для дорогих компонентов ✓ Применяй useMemo только если есть реальные замеры ✓ Выдели состояние на нужный уровень ✓ Используй правильные key в списках

✗ Не оптимизируй заранее (premature optimization) ✗ Не оборачивай всё в React.memo ✗ Не используй useMemo везде ✗ Не игнорируй зависимости в хуках

Оптимизация перерендеров — важный навык для создания быстрых React приложений.