Я пытаюсь сделать простой пример, который следует за примером React Hooks в документе на странице https://reactjs.org/docs/hooks-reference.html#usecallback.
Без useCallback код работает как в этом примере:
import React, { useCallback } from "react";
function Test(props) {
function doSomething(a, b) {
console.info("doSomething called");
return a + b;
}
return (
<div>
{Array.from({ length: 3 }).map(() => (
<div>{doSomething('aaa','bbb')}</div>
))}
</div>
);
}
export default Test;
Однако, когда я добавляю, как мне кажется, правильный код для useCallback следующим образом, я получаю сообщение об ошибке (a не определено)
import React, { useCallback } from "react";
function Test(props) {
function doSomething(a, b) {
console.info("doSomething called");
return a + b;
}
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
return (
<div>
{Array.from({ length: 3 }).map(() => (
<div>{memoizedCallback("aaa", "bbb")}</div>
))}
</div>
);
}
export default Test;
Код проблемы здесь:





Цель useCallback состоит в том, чтобы иметь возможность использовать свойства или состояние, которые находятся в текущей области видимости и могут измениться при повторном рендеринге. Затем массив зависимостей сообщает React, когда вам нужна новая версия обратного вызова. Если вы пытаетесь запомнить дорогостоящее вычисление, вам нужно вместо этого использовать useMemo.
В приведенном ниже примере показаны различия между useCallback и useMemo и последствия их неиспользования. В этом примере я использую React.memo, чтобы предотвратить повторный рендеринг Child, если его свойства или состояние не изменятся. Это позволяет увидеть преимущества useCallback. Теперь, если Child получит новый реквизит onClick, это вызовет повторный рендеринг.
Дочерний элемент 1 получает не запомненный обратный вызов onClick, поэтому всякий раз, когда родительский компонент выполняет повторную визуализацию, дочерний элемент 1 всегда получает новую функцию onClick, поэтому он вынужден повторно отображать.
Ребенок 2 использует запомненный обратный вызов onClick, возвращенный из useCallback, а ребенок 3 использует эквивалент через useMemo, чтобы продемонстрировать значение
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs)
Для дочерних элементов 2 и 3 обратный вызов по-прежнему выполняется каждый раз, когда вы нажимаете на дочерние элементы 2 или 3, useCallback просто гарантирует, что передается одна и та же версия функции onClick, когда зависимости не изменились.
Следующая часть дисплея помогает указать, что происходит:
nonMemoizedCallback === memoizedCallback: false|true
Отдельно я показываю 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.info("recomputing expensive thing", a);
return a + b;
};
const somethingExpensiveBasedOnA = computeSomethingExpensiveBasedOnA();
const memoizedSomethingExpensiveBasedOnA = useMemo(
() => computeSomethingExpensiveBasedOnA(),
[a]
);
const nonMemoizedCallback = suffix => {
console.info(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);
Вот связанный ответ: React Hooks useCallback заставляет дочерний элемент повторно отображать
Вы просто неправильно понимаете цель useCallback. useCallback не запоминает результаты функции — он запоминает саму функцию. В первом примере нет веской причины для его использования, но во втором примере это предотвратит изменение свойства onClickChild при повторном рендеринге. Если вы хотите запомнить результаты (чтобы предотвратить избыточное выполнение дорогостоящей функции), вы должны использовать useMemo.
ты так прав, я не понимаю. Меня поставил в тупик этот комментарий в документах: useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs). Я собираю учебные материалы и немного теряюсь, ищу хорошие примеры.
Я заменил свой пример другим, который больше не основан на вашем коде, но он должен помочь прояснить ситуацию, если вы потратите достаточно времени на его изучение, взаимодействие с ним и просмотр результатов.
В первом примере он по-прежнему выводит console.info 3 раза. Что я не получаю? (трудный вопрос, я думаю)