Мемоизация — это техника оптимизации, при которой результат вычисления сохраняется и повторно используется при последующих вызовах, если входные данные не изменились. В контексте React-экосистемы мемоизация особенно важна для предотвращения лишних перерасчётов и повторных рендеров.
В библиотеке React Aria мемоизация играет критическую роль по нескольким причинам:
props), которые передаются в элементы
интерфейса;Без мемоизации каждый рендер компонента создавал бы новые объекты и функции, что приводило бы к:
React.memo;Поэтому React Aria активно использует мемоизацию внутри своих хуков и предполагает корректное применение мемоизации в пользовательском коде.
React Aria предоставляет низкоуровневые хуки, которые возвращают:
props для DOM-элементовrefs)Типичный результат хука выглядит следующим образом:
let {buttonProps} = useButton(props, ref);
buttonProps представляет собой объект:
{
role: "button",
tabIndex: 0,
onKeyDown: fn,
onPressStart: fn,
onPressEnd: fn
}
Если объект создаётся заново на каждом рендере, React воспринимает его как новое значение, даже если фактическое содержимое не изменилось.
В результате:
propsПоэтому React Aria применяет мемоизацию на уровне:
В React существует несколько механизмов мемоизации, которые активно используются при работе с React Aria.
Мемоизирует результат вычисления.
const memoizedValue = useMemo(() => computeValue(a, b), [a, b]);
Пересчёт происходит только при изменении зависимостей.
Мемоизирует функцию.
const handleClick = useCallback(() => {
doSomething(value);
}, [value]);
Мемоизирует компонент, предотвращая повторный рендер при неизменных props.
const Button = React.memo(function Button(props) {
...
});
Эти инструменты применяются совместно с React Aria для стабилизации значений.
Одной из ключевых задач является стабилизация объектов
props, передаваемых в DOM.
Пример без мемоизации:
function MyButton(props) {
const ref = useRef();
const {buttonProps} = useButton({
onPress: () => console.log("click")
}, ref);
return <button {...buttonProps} ref={ref} />;
}
Каждый рендер создаёт новый объект:
{
onPress: () => console.log("click")
}
Хук useButton будет воспринимать это как изменение
props.
Стабилизация функции:
function MyButton(props) {
const ref = useRef();
const handlePress = useCallback(() => {
console.log("click");
}, []);
const {buttonProps} = useButton({
onPress: handlePress
}, ref);
return <button {...buttonProps} ref={ref} />;
}
Теперь ссылка на функцию остаётся неизменной.
Многие хуки React Aria принимают объект конфигурации.
Пример:
useButton({
isDisabled,
onPress,
elementType
}, ref);
Если объект создаётся inline, он будет новым на каждом рендере.
useButton({
isDisabled: disabled,
onPress: handlePress
}, ref);
React создаёт новый объект при каждом вызове функции компонента.
const buttonConfig = useMemo(() => ({
isDisabled: disabled,
onPress: handlePress
}), [disabled, handlePress]);
useButton(buttonConfig, ref);
Теперь объект стабилен между рендерами.
React Aria активно работает с DOM-элементами через
ref.
Создание ref внутри рендера недопустимо:
const ref = React.createRef();
Это создаёт новый ref каждый рендер.
Правильный способ:
const ref = useRef(null);
useRef гарантирует стабильную ссылку между
рендерами.
Некоторые компоненты интерфейса требуют вычисления производных значений.
Например:
Без мемоизации эти вычисления выполняются при каждом рендере.
const visibleItems = items.filter(item => item.visible);
При большом массиве это может быть дорогостоящей операцией.
const visibleItems = useMemo(() => {
return items.filter(item => item.visible);
}, [items]);
React Aria активно использует обработчики:
onPressonFocusonBluronKeyDownonHoverStartonHoverEndСоздание новых функций может нарушить оптимизации.
<button
onCl ick={() => setOpen(true)}
>
Каждый рендер создаёт новую функцию.
const handleOpen = useCallback(() => {
setOpen(true);
}, []);
<button onCl ick={handleOpen}>
При разработке собственных хуков поверх React Aria важно стабилизировать возвращаемые значения.
function useMyButton(props) {
const ref = useRef();
const {buttonProps} = useButton(props, ref);
return {
ref,
buttonProps
};
}
Если возвращать объект напрямую, он будет новым на каждом рендере.
function useMyButton(props) {
const ref = useRef();
const {buttonProps} = useButton(props, ref);
return useMemo(() => ({
ref,
buttonProps
}), [buttonProps]);
}
Теперь объект возвращается стабильно.
Некоторые хуки возвращают массивы:
Пример:
const items = data.map(item => ({
key: item.id,
label: item.name
}));
Массив будет новым при каждом рендере.
const items = useMemo(() => {
return data.map(item => ({
key: item.id,
label: item.name
}));
}, [data]);
Компоненты, использующие React Aria, часто оборачиваются в
React.memo.
const MenuItem = React.memo(function MenuItem(props) {
...
});
Если props стабилизированы мемоизацией:
Особенно важна мемоизация в:
ListBox)Menu)Table)TreeView)ComboBox)В этих компонентах может быть:
Без мемоизации возможны:
React Aria часто работает совместно с контекстом.
Пример:
const Context = React.createContext();
Передача значения:
<Context.Provider value={{state, actions}}>
Если объект создаётся заново, все потребители контекста перерендерятся.
const contextValue = useMemo(() => ({
state,
actions
}), [state, actions]);
<Context.Provider value={contextValue}>
Чрезмерная мемоизация может усложнить код.
Мемоизация не требуется для:
Например:
const count = items.length;
Мемоизация здесь бессмысленна.
Основные правила применения мемоизации в проектах с React Aria:
React.memo для элементов списковuseMemo для дорогих
вычисленийТакой подход позволяет поддерживать: