Как устранить лишние перерендеры

Ответ

Используй React.memo для мемоизации компонентов, если пропсы не изменились. Для сложных вычислений применяй useMemo, а для колбэков — useCallback.

Пример:

const ExpensiveComponent = React.memo(({ data }) => {
  const processedData = useMemo(() => heavyComputation(data), [data]);
  return <div>{processedData}</div>;
});

const Parent = () => {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => setCount(c => c + 1), []);

  return (
    <>
      <ExpensiveComponent data={count} />
      <button onClick={handleClick}>Increment</button>
    </>
  );
};

Проверяй лишние рендеры через React DevTools (вкладка Profiler). Убедись, что стейт и пропсы действительно меняются перед рендером. Для глобального стейта (Redux, Context) используй селекторы или разбивай контексты, чтобы обновлялись только нужные компоненты.

Ответ 18+ 🔞

Слушай, а вот представь: пишешь ты компонент, он вроде и не тяжёлый, но рендерится как сумасшедший от каждого чиха родителя. Ну, блядь, достало уже! Так вот, чтобы эта мартышлюшка не дёргалась попусту, есть у нас React.memo.

Это как будто ты говоришь Реакту: «Чувак, смотри, у меня пропсы те же самые, нихуя не поменялись. Не перерисовывай меня, а?». Обернул компонент — и всё, он теперь сравнивает пропсы поверхностно. Если они те же — рендер проскакивает.

А теперь, хуй с горы, у тебя внутри компонента есть какая-то ебанутая вычислялка, которая каждый раз пересчитывает овердохуище данных. Ты её каждый рендер вызываешь, а она тебе процессор жрёт. Вот тут в игру вступает useMemo. Засунул туда эту функцию и зависимости — и он тебе результат кеширует. Пока зависимости не ебнулись, будет отдавать старое значение, не пересчитывая. Красота, ёпта!

И третий акт в этом ёперном театре — useCallback. Допустим, ты прокидываешь колбэк в мемоизированный дочерний компонент. А этот колбэк у тебя каждый раз создаётся заново. Для Реакта это новая ссылка, значит, пропс изменился, значит, надо перерендерить. Пиздец! Оберни колбэк в useCallback, и ссылка сохранится, пока зависимости в массиве не поменяются. Дочерний компонент успокоится.

Вот тебе пример, как это всё в паре работает, чтобы ты не ебал мозги:

const ExpensiveComponent = React.memo(({ data }) => {
  const processedData = useMemo(() => heavyComputation(data), [data]);
  return <div>{processedData}</div>;
});

const Parent = () => {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => setCount(c => c + 1), []);

  return (
    <>
      <ExpensiveComponent data={count} />
      <button onClick={handleClick}>Increment</button>
    </>
  );
};

Видишь? ExpensiveComponent обёрнут в memo. Ему передают data. Внутри него useMemo кеширует результат тяжёлой функции, пока data та же. А колбэк на кнопке завернут в useCallback, так что он не сломает мемоизацию, если Parent вдруг перерендерится по другим причинам.

И главное, блядь, не верь на слово! Открой React DevTools, вкладку Profiler, и посмотри, какие компоненты у тебя там лишний раз дергаются. Убедись, что стейт и пропсы реально меняются, а не просто кажется.

А если у тебя там глобальный стейт, типа Redux или Context, который на весь чертог развешан — тут отдельная песня. Не вешай всё в один контекст, а то любое изменение будет всю приложуху трясти. Разбивай на мелкие контексты или используй селекторы (типа reselect), чтобы вычислялось только то, что реально нужно конкретному компоненту. Иначе получишь пиздопроебибну перформанса. Думай головой!