Цель useCallback
состоит в том, чтобы иметь возможность использовать реквизит или состояние, которые находятся в текущей области, и которые могут измениться при повторном рендеринге.Затем массив зависимостей сообщает React, когда вам нужна новая версия обратного вызова.Если вы пытаетесь запоминать дорогостоящие вычисления, вам нужно использовать useMemo
.
В приведенном ниже примере демонстрируются различия между useCallback
и useMemo
и последствия их неиспользования.В этом примере я использую React.memo
для предотвращения повторного рендеринга Child
, если только его реквизиты или состояние не изменятся.Это позволяет увидеть преимущества useCallback
.Теперь, если Child
получит новую onClick
опору, это вызовет повторный рендеринг.
Дочерний 1 получает незарегистрированный обратный вызов onClick
, поэтому всякий раз, когда родительский компонент повторно рендерит, Дочерний 1всегда получает новую функцию onClick
, поэтому она вынуждена повторно выполнить рендеринг.
дочерний элемент 2 использует запомненный обратный вызов onClick
, возвращаемый из useCallback
, а дочерний 3 использует эквивалент через useMemo
дляпродемонстрировать значение
useCallback (fn, input) эквивалентно useMemo (() => fn, input)
Для дочерних элементов 2 и 3 обратный вызов по-прежнему получаетвыполняется каждый раз, когда вы щелкаете дочерний элемент 2 или 3, useCallback
просто гарантирует, что одна и та же версия функции onClick
будет передана, когда зависимости не изменились.
Следующая часть экрана помогает указать, что происходит:
nonMemoizedCallback === memoizedCallback: false | true
Отдельно яm с отображением somethingExpensiveBasedOnA
и записанной версией с использованием useMemo
.В демонстрационных целях я использую неверный массив зависимостей (я намеренно пропустил b
), чтобы вы могли видеть, что запомненная версия не изменяется при изменении b
, но меняется при изменении a
.Незамеченная версия изменяется всякий раз, когда a
или b
изменяется.
import ReactDOM from "react-dom";
import React, {
useRef,
useMemo,
useEffect,
useState,
useCallback
} from "react";
const Child = React.memo(({ onClick, suffix }) => {
const numRendersRef = useRef(1);
useEffect(() => {
numRendersRef.current++;
});
return (
<div onClick={() => onClick(suffix)}>
Click Me to log a and {suffix} and change b. Number of Renders:{" "}
{numRendersRef.current}
</div>
);
});
function App(props) {
const [a, setA] = useState("aaa");
const [b, setB] = useState("bbb");
const computeSomethingExpensiveBasedOnA = () => {
console.log("recomputing expensive thing", a);
return a + b;
};
const somethingExpensiveBasedOnA = computeSomethingExpensiveBasedOnA();
const memoizedSomethingExpensiveBasedOnA = useMemo(
() => computeSomethingExpensiveBasedOnA(),
[a]
);
const nonMemoizedCallback = suffix => {
console.log(a + suffix);
setB(prev => prev + "b");
};
const memoizedCallback = useCallback(nonMemoizedCallback, [a]);
const memoizedCallbackUsingMemo = useMemo(() => nonMemoizedCallback, [a]);
return (
<div>
A: {a}
<br />
B: {b}
<br />
nonMemoizedCallback === memoizedCallback:{" "}
{String(nonMemoizedCallback === memoizedCallback)}
<br />
somethingExpensiveBasedOnA: {somethingExpensiveBasedOnA}
<br />
memoizedSomethingExpensiveBasedOnA: {memoizedSomethingExpensiveBasedOnA}
<br />
<br />
<div onClick={() => setA(a + "a")}>Click Me to change a</div>
<br />
<Child onClick={nonMemoizedCallback} suffix="1" />
<Child onClick={memoizedCallback} suffix="2" />
<Child onClick={memoizedCallbackUsingMemo} suffix="3" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
![Edit useCallback and useMemo](https://codesandbox.io/static/img/play-codesandbox.svg)
Вот соответствующий ответ: React HooksuseCallback приводит к повторному рендерингу child