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

Типизация компонентов, передаваемых в React.memo

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

Почему важна типизация мемоизированных компонентов?

Представьте, что вы строите небоскрёб. Вы хотите быть уверены, что каждое его основание достаточно крепкое, а иначе всё может рухнуть. В TypeScript эту функцию берёт на себя типизация, и она становится особенно важной, когда мы используем оптимизационные инструменты вроде React.memo. Без корректной типизации вы рискуете столкнуться с неожиданными ошибками при передаче пропсов.

Существует несколько ключевых моментов, которые требуют особого внимания при типизации компонентов:

  • Вы должны точно описать пропсы, которые принимает компонент.
  • Убедиться, что типы соответствуют тем, которые ожидают React.memo.
  • Избежать ошибок при передаче кастомных пропсов или интерфейсов.

Теперь перейдём к практике, где всё станет на удивление понятным.

Типизация мемоизированных компонентов: основы

Оборачиваем простой компонент

Для начала давайте создадим базовый функциональный компонент, типизируем его с использованием TypeScript и обернём в React.memo.

import React from 'react';

interface ButtonProps {
  label: string;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
  console.log('Рендер кнопки');
  return <button onClick={onClick}>{label}</button>;
};

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

export default MemoizedButton;

Что здесь происходит?

  1. Мы создали интерфейс для пропсов ButtonProps, который описывает label (строка) и onClick (функция без аргументов).
  2. Использовали React.FC для типизации функционального компонента Button.
  3. Затем обернули компонент в React.memo, чтобы предотвратить его рендеринг, если пропсы не изменились.

Теперь компонент MemoizedButton будет оптимизировать рендер, а TypeScript позаботится, чтобы мы передавали исключительно нужные пропсы.

Упрощение без React.FC

Кстати, вы можете не использовать React.FC. Это не обязательно и иногда даже излишне, если вам не нужна типизация children. Вот как это будет выглядеть:

const Button = ({ label, onClick }: ButtonProps) => {
  console.log('Рендер кнопки');
  return <button onClick={onClick}>{label}</button>;
};

const MemoizedButton = React.memo<ButtonProps>(Button);

Что делать с кастомной проверкой пропсов?

По умолчанию React.memo использует поверхностное сравнение для проверки изменений в пропсах. Но если вы хотите использовать свою логику сравнения, можно передать кастомную функцию в React.memo.

Пример с кастомной функцией сравнения

Добавим функцию, которая сравнивает пропсы вручную:

const areEqual = (prevProps: ButtonProps, nextProps: ButtonProps) => {
  return prevProps.label === nextProps.label && prevProps.onClick === nextProps.onClick;
};

const MemoizedButton = React.memo(Button, areEqual);

Здесь:

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

Типизация с дополнительными особенностями

Работа с необязательными пропсами

Если у вас есть пропсы, которые могут быть необязательными, старайтесь чётко указывать это в интерфейсе:

interface ButtonProps {
  label: string;
  onClick?: () => void; // не обязательный пропс
}

const Button = ({ label, onClick }: ButtonProps) => {
  console.log('Рендер кнопки');
  return <button onClick={onClick}>{label}</button>;
};

const MemoizedButton = React.memo(Button);

TypeScript гарантирует, что вы не забудете учесть необязательные поля, что спасёт от неожиданных багов.

Передача generic-типов в компоненты

Что если ваш компонент принимает данные разного типа? Например, вам нужно сделать универсальный компонент списка.

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

const List = <T,>({ items, renderItem }: ListProps<T>) => {
  console.log('Рендер списка');
  return <ul>{items.map(renderItem)}</ul>;
};

// Типизация обёрнутого компонента
const MemoizedList = React.memo(List) as typeof List;

export default MemoizedList;

Эта магия позволяет использовать generics в компонентах, обёрнутых в React.memo.

Часто задаваемые вопросы и ошибки

Почему возникают ошибки типа при оборачивании компонента?

Многие сталкиваются с проблемой, что TypeScript начинает ругаться после оборачивания компонента в React.memo. Это бывает, если:

  • Компонент изначально типизирован некорректно (например, пропсы не определены).
  • В React.memo не передается тип компонента явно.

Чтобы избежать таких проблем, можно использовать следующий трюк:

const MemoizedButton = React.memo<ButtonProps>((props) => <Button {...props} />);

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

Когда оптимизация избыточна! Если компонент не рендерится часто, использование React.memo может только усложнить ваш код. Например, для компонентов с фиксированными пропсами React.memo не принесёт никакой пользы.

Практическое применение: добавляем мемоизацию в проект

Давайте добавим мемоизацию в наше приложение. Предположим, у нас есть список задач, каждая из которых рендерится компонентом TaskItem.

Реализация компонента TaskItem

interface TaskItemProps {
  id: number;
  title: string;
  completed: boolean;
  onToggle: (id: number) => void;
}

const TaskItem = ({ id, title, completed, onToggle }: TaskItemProps) => {
  console.log(`Рендер задачи #${id}`);
  return (
    <li>
      <input
        type="checkbox"
        checked={completed}
        onChange={() => onToggle(id)}
      />
      {title}
    </li>
  );
};

const MemoizedTaskItem = React.memo(TaskItem);

Использование TaskItem в списке

Теперь мы используем MemoizedTaskItem для оптимальной работы:

interface Task {
  id: number;
  title: string;
  completed: boolean;
}

const TaskList = ({ tasks, onToggle }: { tasks: Task[]; onToggle: (id: number) => void }) => {
  return (
    <ul>
      {tasks.map((task) => (
        <MemoizedTaskItem
          key={task.id}
          id={task.id}
          title={task.title}
          completed={task.completed}
          onToggle={onToggle}
        />
      ))}
    </ul>
  );
};

Советы на будущее

  • Используйте React.memo для предотвращения лишних рендеров, но только когда это действительно нужно.
  • Чётко типизируйте пропсы компонентов, особенно мемоизированных.
  • Не забывайте об управлении зависимостями, если используете кастомные функции сравнения.
  • Не бойтесь использовать такие фишки TypeScript, как generics, для повышения гибкости компонентов.

Типизация в связке с React.memo позволяет вам писать надёжный и эффективный код, но, как и любая оптимизация, она должна применяться с умом. "Меньше значит больше" — это ваш девиз.

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