JavaRush /Курсы /Модуль 3: React /Использование React.memo — мемоизация компонентов и предо...

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

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

Что такое React.memo и как он работает?

Прежде чем погрузиться в детали, представьте, что ваш React-компонент — это шеф-повар. Каждый раз, когда поступает заказ (рендеринг), он готовит одно и то же блюдо заново, даже если вчера его уже готовил. Это неэффективно. React.memo — это как холодильник: шеф может посмотреть в него и сказать: "Эй, это блюдо уже готово, я просто достану его из холодильника!"

React.memo — это HOC (Higher-Order Component), который оборачивает функциональный компонент и предотвращает его повторный рендеринг, если его пропсы не изменились.

Как это работает?

React.memo сравнивает текущие пропсы с предыдущими. Если пропсы не изменились, React пропускает повторный рендер компонента.

import React from 'react';

// Наш компонент
type Props = { name: string };
const Greeting: React.FC<Props> = ({ name }) => {
  console.log('Рендер компонента Greeting');
  return <h1>Привет, {name}!</h1>;
};

// Оборачиваем компонент в React.memo
const MemoizedGreeting = React.memo(Greeting);

export default MemoizedGreeting;

Используя Greeting в вашем приложении:

import React, { useState } from 'react';
import MemoizedGreeting from './MemoizedGreeting';

const App: React.FC = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <MemoizedGreeting name="Иван" />
      <button onClick={() => setCount(count + 1)}>Счёт: {count}</button>
    </div>
  );
};

Вывод в консоли:

Рендер компонента Greeting

Каждый раз, когда вы нажимаете кнопку, компонент MemoizedGreeting не рендерится снова, так как его пропсы name: "Иван" не изменились.

Примеры использования React.memo

Пример 1. Лишние рендеры в списке

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

import React from 'react';

type ItemProps = { item: string };
const ListItem: React.FC<ItemProps> = React.memo(({ item }) => {
  console.log(`Рендер: ${item}`);
  return <li>{item}</li>;
});

type ListProps = { items: string[] };
const ItemList: React.FC<ListProps> = ({ items }) => {
  return (
    <ul>
      {items.map((item, index) => (
        <ListItem key={index} item={item} />
      ))}
    </ul>
  );
};

export default ItemList;

Пример 2. Изменение ближайшего родителя

Если родительский компонент изменяется, это может триггерить лишние рендеры у дочерних компонентов. С React.memo мы предотвращаем такую цепочку рендеров.

import React, { useState } from 'react';
import ItemList from './ItemList';

const App: React.FC = () => {
  const [count, setCount] = useState(0);
  const items = ['Яблоко', 'Банан', 'Груша'];

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Счёт: {count}</button
      <ItemList items={items} />
    </div>
  );
};

На каждый клик кнопки, ItemList рендерится, но сами ListItem не перерисовываются, так как их пропсы не меняются.

Ограничения и лучшие практики

Хорошее правило: оптимизируйте только то, что имеет значение. Если компонент рендерится очень быстро (например, не содержит сложной логики), использование React.memo может быть избыточным и даже замедлит приложение из-за дополнительной проверки пропсов.

Ограничения React.memo

  1. Сравнение только пропсов: React.memo не может предотвратить рендер, если компонент зависит от состояния или контекста.
  2. Поверхностное сравнение: по умолчанию, сравнение пропсов выполняется "поверхностно". Если пропс — это объект или массив, React сравнит только ссылки на них, а не их содержимое.
import React from 'react';

// Пример с объектом
type UserProps = { user: { name: string } };
const UserGreeting: React.FC<UserProps> = React.memo(({ user }) => {
  console.log('Рендер UserGreeting');
  return <h1>Привет, {user.name}!</h1>;
});

const App: React.FC = () => {
  const user = { name: 'Иван' };

  return <UserGreeting user={user} />;
};

Даже если user не изменится, React снова отрендерит компонент, так как сравниваются ссылки на объект, а не его содержимое.

Решение: кастомный areEqual

React.memo принимает второй аргумент — функцию areEqual, чтобы контролировать процесс сравнения пропсов.

const UserGreeting = React.memo(
  ({ user }: UserProps) => {
    console.log('Рендер UserGreeting');
    return <h1>Привет, {user.name}!</h1>;
  },
  (prevProps, nextProps) => {
    return prevProps.user.name === nextProps.user.name;
  }
);

Теперь рендер компонента будет предотвращён, если имя пользователя не изменилось.

Лучшие практики использования React.memo

  1. Используйте React.memo, если компонент:

    • Получает большие данные в пропсах и нечасто изменяется.
    • Выполняет тяжёлые вычисления или сложную логику.
    • Используется часто, но изменения в пропсах происходят редко.
  2. Избегайте мемоизации компонентов с контекстом или локальным состояниемReact.memo никак не влияет на изменения в useContext или useState.

  3. Не применяйте React.memo к каждому компоненту подряд. Оптимизация — это весело, но только если она действительно нужна. Помните: "Меньше — значит больше".

Реальное применение: мемоизация списка задач

Теперь добавим React.memo в наше приложение списка задач.

Компонент задачи

type TaskProps = { task: string };
const TaskItem: React.FC<TaskProps> = React.memo(({ task }) => {
  console.log(`Рендер задачи: ${task}`);
  return <li>{task}</li>;
});

Список задач

type TaskListProps = { tasks: string[] };
const TaskList: React.FC<TaskListProps> = ({ tasks }) => {
  return (
    <ul>
      {tasks.map((task, index) => (
        <TaskItem key={index} task={task} />
      ))}
    </ul>
  );
};

Приложение

const App: React.FC = () => {
  const [tasks, setTasks] = React.useState(['Купить молоко', 'Выгулять собаку']);
  const [count, setCount] = React.useState(0);

  const addTask = () => {
    setTasks([...tasks, `Новая задача №${tasks.length + 1}`]);
  };

  return (
    <div>
      <h1>Список задач</h1>
      <TaskList tasks={tasks} />
      <button onClick={addTask}>Добавить задачу</button>
      <button onClick={() => setCount(count + 1)}>Счёт: {count}</button>
    </div>
  );
};

Теперь TaskItem рендерится только тогда, когда к списку добавляется новая задача.

В будущем мы еще раз коснемся это темы, так что если вы поняли только половину - это уже отлично :)

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