Почему важна типизация мемоизированных компонентов?
Представьте, что вы строите небоскрёб. Вы хотите быть уверены, что каждое его основание достаточно крепкое, а иначе всё может рухнуть. В 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;
Что здесь происходит?
- Мы создали интерфейс для пропсов
ButtonProps, который описываетlabel(строка) иonClick(функция без аргументов). - Использовали
React.FCдля типизации функционального компонентаButton. - Затем обернули компонент в
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 позволяет вам писать надёжный и эффективный код, но, как и любая оптимизация, она должна применяться с умом. "Меньше значит больше" — это ваш девиз.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ