React.memo — это высокоуровневая обертка для функциональных компонентов в React, которая позволяет избежать ненужного повторного рендера, если пропсы компонента не изменились. Она сравнивает предыдущие и новые значения пропсов с помощью поверхностного сравнения (shallow comparison). Если значения не изменились, React повторно не вызывает функцию компонента, что экономит ресурсы и повышает производительность приложения.
Синтаксис прост:
import React from "react";
const MyComponent = React.memo(function MyComponent({ title, count }) {
console.log("Rendering MyComponent");
return (
<div>
<h1>{title}</h1>
<p>{count}</p>
</div>
);
});
В этом примере MyComponent будет рендериться только
при изменении title или count. Если
родительский компонент обновится, но значения пропсов останутся
прежними, повторного рендера не произойдет.
По умолчанию React.memo использует поверхностное сравнение объектов и массивов. Это означает, что для сложных структур данных:
const obj = { a: 1 };
const arr = [1, 2, 3];
<MyComponent data={obj} list={arr} />
даже если содержимое объектов не меняется, но создается новый объект или массив при каждом рендере родителя, React.memo все равно вызовет повторный рендер, потому что ссылки на объекты изменились.
Для контроля поведения при сложных пропсах можно использовать второй аргумент — функцию сравнения:
const MyComponent = React.memo(
function MyComponent({ data }) {
console.log("Rendering with custom comparison");
return <div>{data.value}</div>;
},
(prevProps, nextProps) => prevProps.data.value === nextProps.data.value
);
Функция сравнения должна возвращать true, если пропсы
считаются одинаковыми и рендер не требуется, или false,
если компонент должен перерендериться.
Radix UI предоставляет набор полностью управляемых компонентов
интерфейса, таких как Dialog, Popover,
DropdownMenu, Tabs и другие. Часто эти
компоненты имеют большое количество вложенных элементов и обработчиков
событий, что может замедлять рендер при частых обновлениях
состояния.
Использование React.memo особенно полезно для:
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const MenuItem = React.memo(({ label, onSelect }) => (
<DropdownMenu.Item onSel ect={onSelect}>
{label}
</DropdownMenu.Item>
));
Такой подход предотвращает перерисовку всех элементов меню при каждом изменении состояния родителя, снижая нагрузку на виртуальный DOM.
Часто повторный рендер компонентов происходит из-за того, что функции передаются как пропсы, создавая новые ссылки при каждом рендере. Для устранения этого используются useCallback и useMemo:
import { useCallback, useState } from "react";
const Parent = () => {
const [count, setCount] = useState(0);
const handleSelect = useCallback((value) => {
console.log("Selected", value);
}, []);
return (
<div>
<button onCl ick={() => setCount(count + 1)}>Increment</button>
<MenuItem label="Option 1" onSel ect={handleSelect} />
</div>
);
};
useCallback сохраняет ссылку на функцию между рендерами, предотвращая ненужные обновления дочерних компонентов, обернутых в React.memo. Аналогично, useMemo можно использовать для вычислений сложных пропсов:
const options = useMemo(() => generateOptions(data), [data]);
Radix UI компоненты часто имеют внутреннее состояние и контролируемые свойства. Важно понимать, что React.memo не предотвращает ререндер внутреннего состояния Radix UI компонентов, но помогает уменьшить рендер их оберток и вложенных элементов:
import * as Tabs from '@radix-ui/react-tabs';
const TabContent = React.memo(({ tabId }) => {
console.log("Rendering TabContent", tabId);
return <div>Content for {tabId}</div>;
});
const TabsExample = () => (
<Tabs.Root defaultValue="tab1">
<Tabs.List>
<Tabs.Trigger value="tab1">Tab 1</Tabs.Trigger>
<Tabs.Trigger value="tab2">Tab 2</Tabs.Trigger>
</Tabs.List>
<TabContent tabId="tab1" />
<TabContent tabId="tab2" />
</Tabs.Root>
);
Даже если родительский компонент TabsExample ререндерится,
TabContent будет обновляться только при изменении
пропса tabId.
Эти подходы обеспечивают значительное улучшение отклика интерфейса при больших динамических приложениях, особенно при активном использовании компонентов из Radix UI.