JavaRush /Курсы /Модуль 3: React /Использование React.memo для предотвращения лишних рендер...

Использование React.memo для предотвращения лишних рендеров

Модуль 3: React
10 уровень , 5 лекция
Открыта

Введение

Каждый раз, когда компонент перерисовывается (даже если не нужно), React тратит вычислительные ресурсы: проходит через виртуальное DOM, сравнивает изменения и обновляет настоящий DOM. Если у вас много компонентов, это может существенно замедлить работу приложения.

Типичная ситуация

Допустим, у нас есть следующий компонент:

const Profile = ({ name }: { name: string }) => {
  console.log(`Rendered Profile: ${name}`);
  return <div>{name}</div>;
};

const App = () => {
  const [counter, setCounter] = React.useState(0);

  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      <Profile name="John Doe" />
    </div>
  );
};

Каждый раз, когда пользователь нажимает кнопку, обновляется состояние counter, и весь компонент App рендерится заново. Включая наш Profile. Хотя name не изменилось, компонент всё равно перерисовывается. Давайте это исправим!

Знакомьтесь, React.memo!

Что это такое?

React.memo — это функция высшего порядка, которая оптимизирует функциональные компоненты, предотвращая их перерисовку, если пропсы не изменились.

Если провести аналогию, то React.memo — это охранник на входе в ваш компонент. Он проверяет: "Пропсы изменились? Нет? Тогда сиди, дорогой компонент, и отдыхай, не перерисовывайся зря!".

Как использовать?

Синтаксис — проще некуда: просто оборачиваем компонент в React.memo.

const Profile = React.memo(({ name }: { name: string }) => {
  console.log(`Rendered Profile: ${name}`);
  return <div>{name}</div>;
});

Теперь, если name не изменяется, Profile больше не будет перерисовываться.

Пример с App

Давайте вернемся к нашему приложению:

const Profile = React.memo(({ name }: { name: string }) => {
  console.log(`Rendered Profile: ${name}`);
  return <div>{name}</div>;
});

const App = () => {
  const [counter, setCounter] = React.useState(0);

  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      <Profile name="John Doe" />
    </div>
  );
};

Теперь, если нажать кнопку, Profile больше не перерисуется, а значит, лог Rendered Profile: John Doe появится только один раз.

Как работает React.memo?

React сравнивает предыдущие пропсы с текущими и, если они одинаковы (по поверхностному сравнению), компонент не рендерится заново. Стоит помнить, что сравнение выполняется только по ссылкам, а не по значению. Это значит, что с объектами и массивами нужно быть осторожным.

Чтобы показать, как это работает, попробуем передать объект в качестве пропса:

const Profile = React.memo(({ user }: { user: { name: string } }) => {
  console.log(`Rendered Profile: ${user.name}`);
  return <div>{user.name}</div>;
});

const App = () => {
  const [counter, setCounter] = React.useState(0);
  const user = { name: "John Doe" };

  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Click me</button>
      <Profile user={user} />
    </div>
  );
};

Вы удивитесь, но Profile будет перерисовываться каждый раз! Это случается потому, что при каждом рендере создается новый объект user, и его ссылка изменяется. Чтобы исправить это, нужно мемоизировать user с помощью useMemo.

Типизация компонентов с React.memo

Конечно же, мы не можем обойти стороной TypeScript. Давайте разберемся, как типизировать компоненты с React.memo.

Простые компоненты

Если у вас обычный функциональный компонент, TypeScript автоматически подхватывает его типы:

type ProfileProps = { name: string };

const Profile: React.FC<ProfileProps> = React.memo(({ name }) => {
  return <div>{name}</div>;
});

Типы ProfileProps автоматически применяются к компоненту. Супер!

Компоненты с forwardRef

Если вы используете React.forwardRef, типизация становится немного сложнее. Пример:

type InputProps = { label: string };

const Input = React.memo(
  React.forwardRef<HTMLInputElement, InputProps>(({ label }, ref) => {
    return (
      <div>
        <label>{label}</label>
        <input ref={ref} />
      </div>
    );
  })
);

Здесь мы комбинируем React.forwardRef и React.memo. Обратите внимание, что forwardRef всегда идет внутри React.memo.

Когда не нужно использовать React.memo

Само собой, React.memo не всегда полезен. Вот моменты, когда лучше обойтись без него:

  1. Маленькие компоненты — если ваши компоненты очень быстро рендерятся, React.memo может лишь добавить накладные расходы.
  2. Частые изменения пропсов — если пропсы меняются практически при каждом рендере, смысла в мемоизации нет.
  3. Сложные пропсы (например, объекты или функции) — помните, что поверхностное сравнение работает только с примитивами. Если передаете объекты или массивы, используйте useMemo или useCallback.

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

Давайте создадим небольшой пример, где React.memo действительно полезен. Представьте список задач, который перерисовывается при каждом добавлении новой задачи.

type TaskProps = { task: string };

const Task = React.memo(({ task }: TaskProps) => {
  console.log(`Rendered Task: ${task}`);
  return <li>{task}</li>;
});

const App = () => {
  const [tasks, setTasks] = React.useState<string[]>([]);
  const [input, setInput] = React.useState("");

  const addTask = () => {
    setTasks([...tasks, input]);
    setInput("");
  };

  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={addTask}>Add Task</button>
      <ul>
        {tasks.map((task, index) => (
          <Task key={index} task={task} />
        ))}
      </ul>
    </div>
  );
};

Что происходит?

С React.memo задачи, которые уже находятся в списке, больше не будут перерисовываться, что заметно улучшает производительность. Вы можете проверить это, увидев, что console.log выводится только для новых задач.

Частые ошибки при использовании React.memo

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

  • Оборачивание всех компонентов подряд, даже тех, что рендерятся за миллисекунды.
  • Игнорирование того, что объекты и функции меняются по ссылке.
  • Ожидание магического ускорения, которое не произойдет.

Теперь, когда вы знаете, как работает React.memo, вы становитесь на шаг ближе к созданию действительно оптимизированных React-приложений. А если кто-то спросит: "А ты знаешь, как избежать лишних рендеров?", — вы с гордостью ответите: "Конечно, React.memo! 😉".

1
Задача
Модуль 3: React, 10 уровень, 5 лекция
Недоступна
Простое использование React.memo
Простое использование React.memo
1
Задача
Модуль 3: React, 10 уровень, 5 лекция
Недоступна
Мемоизация компонентов с объектами
Мемоизация компонентов с объектами
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ