UseMemo и UseCallback
1) Посмотрим на их особенности
2) Разберем работу useMemo и useCallback с точки зрения памяти
3) Разберем примеры как делать не нужно
4) Посмотрим на бенчмарк
Depth == 1
function App() {
const [a, setA] = useState(1);
const result = useMemo(() => {
console.log("HERE");
return a + "_text";
}, [a]);
return (
<div className="App">
<h1>Hello {result}</h1>
<button onClick={() => setA(2)}>Setup 2</button>
<button onClick={() => setA(1)}>Setup 1</button>
</div>
);
}
Колбеки работают через массив подписок
function isEventSupported(eventNameSuffix) {
if (!canUseDOM) {
return false;
}
const eventName = 'on' + eventNameSuffix;
let isSupported = eventName in document;
if (!isSupported) {
const element = document.createElement('div');
element.setAttribute(eventName, 'return;');
isSupported = typeof element[eventName] === 'function';
}
return isSupported;
}
function handleTopLevel(bookKeeping) {
let targetInst = bookKeeping.targetInst;
let ancestor = targetInst;
do {
if (!ancestor) {
bookKeeping.ancestors.push(ancestor);
break;
}
const root = findRootContainerNode(ancestor);
if (!root) {
break;
}
bookKeeping.ancestors.push(ancestor);
ancestor = getClosestInstanceFromNode(root);
} while (ancestor);
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
runExtractedEventsInBatch(
bookKeeping.topLevelType,
targetInst,
bookKeeping.nativeEvent,
getEventTarget(bookKeeping.nativeEvent),
);
}
}
function MyComponent({ title }) {
return (
<button
onClick={() => {alert('hello world')}}
>
{title}
</button>
);
}
Постоянного addEventListener | removeEventListener не происходит
function MyComponent({ title }) {
return (
<button
onClick={() => {alert('hello world')}}
>
{title}
</button>
);
}
Постоянного addEventListener | removeEventListener не происходит
Добавление и удаление элемента из списка — дешевле
Memo
По-умолчанию работает как PureComponent
const A = memo(function MyComponent({ b }) {
const [a, setA] = useState(1);
const rendered = useRef(0);
rendered.current++;
return (
<div>
<h2>a is {a}</h2>
<h2>b is {b}</h2>
<h2>Component rendered {rendered.current} times</h2>
<button
onClick={() => {
setA(a => a + 1);
}}
>
Change A
</button>
<button
onClick={() => {
setA(a => a);
}}
>
Change A to the same value
</button>
</div>
);
});
<A
b={b.value}
onClick={() => {
setB(b => ({ ...b }));
console.log("here");
}}
/>
1) Memo проверяет state и props
2) Memo НЕ проверяет context
3) state "бойлится" (set для примитивного value с тем же значением не запускает механизм dirty компонентов)
4) memo без доп. параметров не имеет смысла, если есть инлайн колбек
5) memo принимает второй аргумент — функцию-компаратор
function MyComponent() {
const items = useMemo(() => {
сложные вычисления
});
return (
<>
{items.map(item =>
<MyComponent2
data={item}
onClick={() => {}}
/>)}
</>
)
}
MyComponent должен:
1) Кешировать в useMemo и колбеки
2) Убедиться, что MyComponent2 имеет memo
3) Иначе профита от кеширования крайне мало!
MyComponent2 должен:
1) Иметь memo
2) Не опускать проверку на onClick в memo!
Бывает ли профит от ПРОСТО мемоизации?
1) Да
2) Вычисления должны быть такими, что тормоза при тротле уже заметны
Garbage collector
function A({text}) {
const onClick = useCallback(() => {
alert(text);
}, [text]);
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) {
const onClick = useCallback(() => {
alert(text);
}, [text]);
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) {
const onClick = useCallback(() => { // () => {}
alert(text);
}, [text]);
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) {
const onClick = useCallback(() => { // () => {}
alert(text);
}, [text]); // []
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) {
const onClick = useCallback(() => { // () => {}
alert(text);
}, [text]); // []
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) { // () => {}, []
const onClick = useCallback(() => {
alert(text);
}, [text]);
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) { // 1) () => {}
const onClick = useCallback(() => { // 2) () => {}
alert(text);
}, [text]);
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) { // 1) () => {}, []
const onClick = useCallback(() => { // 2) () => {}
alert(text);
}, [text]); // 2) []
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
function A({text}) {
const onClick = () => {
alert(text);
};
return (
<button onClick={onClick}>
Click Me!
</button>
);
}
Benchmark Time