Возможно, я что-то неправильно понял, но useCallback Hook запускается каждый раз, когда происходит повторный рендеринг.
Я передал входные данные - в качестве второго аргумента в useCallback - неизменяемые константы - но возвращенный мемоизированный обратный вызов по-прежнему выполняет мои дорогостоящие вычисления при каждом рендеринге (я почти уверен - вы можете проверить это самостоятельно в фрагменте ниже).
Я изменил useCallback на useMemo — и useMemo работает так, как ожидалось — запускается при изменении переданных входных данных. И действительно запоминает дорогостоящие расчеты.
'use strict';
const { useState, useCallback, useMemo } = React;
const neverChange = 'I never change';
const oneSecond = 1000;
function App() {
const [second, setSecond] = useState(0);
// This ? expensive function executes everytime when render happens:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
// This ? executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${computedCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}
const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };
function expensiveCalc(hook) {
let i = 0;
while (i < tenThousand) i++;
return ++expensiveCalcExecutedTimes[hook];
}
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);<h1>useCallback vs useMemo:</h1>
<div id = "app">Loading...</div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>useCallback(fn, deps) эквивалентно useMemo(() => fn, deps).
Всякий раз, когда вы объявляете функцию со скобками, она вызывает функцию. В результате при каждом обновлении second будет выполняться функция calcCallback.





TL;ДР;
useMemo — запоминать результат расчета между вызовами функции и между рендерамиuseCallback — запомнить сам обратный вызов (референтное равенство) между рендерамиuseRef — хранить данные между рендерингами (обновление не приводит к повторному рендерингу)useState — хранить данные между рендерингами (обновление приведет к повторному рендерингу)Длинная версия:
useMemo фокусируется на том, чтобы избежать сложных вычислений.
useCallback фокусируется на другом: он устраняет проблемы с производительностью, когда встроенные обработчики событий, такие как onClick = {() => { doSomething(...); }, вызывают PureComponent повторную визуализацию дочерних элементов (поскольку выражения функций каждый раз ссылочно разные)
При этом useCallback ближе к useRef, а не к способу запомнить результат вычисления.
Глядя на документы, я согласен, что это выглядит запутанно.
useCallbackwill return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
Пример
Предположим, у нас есть основанный на PureComponent дочерний элемент <Pure />, который будет повторно отображаться только после изменения его props.
Этот код повторно отображает дочерний элемент каждый раз, когда повторно отображается родитель, потому что встроенная функция каждый раз ссылочно отличается:
function Parent({ ... }) {
const [a, setA] = useState(0);
...
return (
...
<Pure onChange = {() => { doSomething(a); }} />
);
}
Мы можем справиться с этим с помощью useCallback:
function Parent({ ... }) {
const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, []);
...
return (
...
<Pure onChange = {onPureChange} />
);
}
Но как только a изменяется, мы обнаруживаем, что функция-обработчик onPureChange, которую мы создали — и React запомнил для нас — по-прежнему указывает на старое значение a! У нас ошибка, а не проблема с производительностью! Это связано с тем, что onPureChange использует замыкание для доступа к переменной a, которая была захвачена при объявлении onPureChange. Чтобы исправить это, нам нужно сообщить React, куда бросить onPureChange, и воссоздать/запомнить (запоминать) новую версию, которая указывает на правильные данные. Мы делаем это, добавляя a в качестве зависимость во второй аргумент `useCallback:
const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Теперь, если a изменено, React перерисовывает компонент. А при ререндере видит, что зависимость для onPureChange другая, и нужно пересоздать/запоминать новую версию коллбэка. Наконец все работает!
Обратите внимание, что не только для PureComponent/React.memo ссылочное равенство может иметь решающее значение при использовании чего-либо в качестве зависимости в useEffect.
Я не думаю, что этот комментарий очень хорошо связал все воедино. Означает ли добавление массива обратного вызова и зависимостей, что <Pure/> не будет перерисовываться, если a не изменился?
поскольку в этом случае обратный вызов onPureChange будет ссылочно таким же, тогда да, <Pure> не будет повторно отображаться при повторном отображении его родителя.
because the inline function is referentially different each time это то, что действительно довело меня до точки зрения. Спасибо!!
Вы вызываете запомненный обратный вызов каждый раз, когда делаете:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
Вот почему количество useCallback увеличивается. Однако функция никогда не меняется, она никогда не создает ***** новый обратный вызов, она всегда одна и та же. Значение useCallback правильно выполняет свою работу.
Давайте внесем некоторые изменения в ваш код, чтобы убедиться, что это правда. Давайте создадим глобальную переменную lastComputedCallback, которая будет отслеживать, возвращается ли новая (другая) функция. Если возвращается новая функция, это означает, что useCallback просто «выполняется снова». Поэтому, когда он снова выполнится, мы вызовем expensiveCalc('useCallback'), поскольку именно так вы считаете, сработало ли useCallback. Я делаю это в приведенном ниже коде, и теперь ясно, что useCallback запоминает, как и ожидалось.
Если вы хотите, чтобы useCallback каждый раз заново создавала функцию, раскомментируйте строку в массиве, которая проходит second. Вы увидите, как он воссоздает функцию.
'use strict';
const { useState, useCallback, useMemo } = React;
const neverChange = 'I never change';
const oneSecond = 1000;
let lastComputedCallback;
function App() {
const [second, setSecond] = useState(0);
// This ? is not expensive, and it will execute every render, this is fine, creating a function every render is about as cheap as setting a variable to true every render.
const computedCallback = useCallback(() => expensiveCalc('useCallback'), [
neverChange,
// second // uncomment this to make it return a new callback every second
]);
if (computedCallback !== lastComputedCallback) {
lastComputedCallback = computedCallback
// This ? executes everytime computedCallback is changed. Running this callback is expensive, that is true.
computedCallback();
}
// This ? executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${expensiveCalcExecutedTimes.useCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}
const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };
function expensiveCalc(hook) {
let i = 0;
while (i < 10000) i++;
return ++expensiveCalcExecutedTimes[hook];
}
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);<h1>useCallback vs useMemo:</h1>
<div id = "app">Loading...</div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>Преимущество useCallback заключается в том, что возвращаемая функция одна и та же, поэтому реагировать не removeEventListener и addEventListener на элемент каждый раз, ЕСЛИ computedCallback не изменится. А computedCallback меняется только при изменении переменных. Таким образом реагировать будет только addEventListener один раз.
Отличный вопрос, я многому научился, отвечая на него.
просто небольшой комментарий к хорошему ответу: основная цель не в addEventListener/removeEventListener(эта операция сама по себе не тяжелая, поскольку не приводит к перекомпоновке/перерисовке DOM), а в том, чтобы избежать повторного рендеринга PureComponent(или с помощью пользовательского shouldComponentUpdate()) дочернего элемента, который использует этот обратный вызов
Спасибо @skyboyer, я понятия не имел о том, что *EventListener стоит дешево, это здорово, что это не вызывает оплавление / рисование! Я всегда думал, что это дорого, поэтому я старался избегать этого. Итак, в случае, если я не перехожу к PureComponent, стоит ли сложность, добавленная useCallback, компромисса с реагированием и дополнительной сложностью DOM remove/addEventListener?
если не использовать PureComponent или пользовательский shouldComponentUpdate для вложенных компонентов, то useCallback не добавит никакого значения (накладные расходы на дополнительную проверку второго аргумента useCallback сведут на нет пропуск дополнительного removeEventListener/addEventListener перемещения)
Вау, супер интересно, спасибо, что поделились этим, это совершенно новый взгляд на то, как *EventListener не дорогая операция для меня.
Один вкладыш для useCallback vs useMemo:
useCallback(fn, deps)is equivalent touseMemo(() => fn, deps).
С помощью useCallback вы запоминаете функции, useMemo запоминает любое вычисленное значение:
const fn = () => 42 // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]) // (1)
const memoFnReturn = useMemo(fn, [dep]) // (2)
(1) вернет запомненную версию fn — одну и ту же ссылку для нескольких рендеров, если dep одинакова. Но каждый раз вы вызыватьmemoFn, это сложное вычисление начинается снова.
(2) будет вызывать fn при каждом изменении dep и запоминать его возвращаемое значение (здесь 42), который затем сохраняется в memoFnReturn.
const App = () => {
const [dep, setDep] = useState(0);
const fn = () => 42 + dep; // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]); // (1)
const memoFnReturn = useMemo(fn, [dep]); // (2)
return (
<div>
<p> memoFn is {typeof memoFn} </p>
<p>
Every call starts new calculation, e.g. {memoFn()} {memoFn()}
</p>
<p>memoFnReturn is {memoFnReturn}</p>
<p>
Only one calculation for same dep, e.g. {memoFnReturn} {memoFnReturn}
</p>
<button onClick = {() => setDep((p) => p + 1)}>Change dep</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity = "sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity = "sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w = " crossorigin = "anonymous"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback, useMemo } = React</script>useMemo и useCallback используют запоминание.
Мне нравится думайте о мемоизации как о запоминании чего-либо.
Хотя и useMemo, и useCallbackзапомнить что-то между рендерит пока зависимости не изменятся, разница только в том, что они запомнить.
useMemo будет запомнить возвращаемым значением из вашей функции.
useCallback будет запомнить вашей фактической функцией.
Источник: В чем разница между useMemo и useCallback?
Замечательный ответ
Мы можем использовать **useCallback** для запоминания функции, что означает, что эта функция будет переопределена только в том случае, если какая-либо из ее зависимостей в массиве зависимостей изменится.
**useMemo(() => computation(a, b), [a, b])** — это хук, который позволяет нам запоминать дорогостоящие вычисления. Учитывая те же зависимости [a, b], после запоминания хук будет возвращать запомненное значение без вызова вычисления (a, b).
This article about different React Memoization Methods: React.memo, useMemo, useCallback, what is different between them with practical examples: https://medium.com/geekculture/great-confusion-about-react-memoization-methods-react-memo-usememo-usecallback-a10ebdd3a316
UseMemo используется для запоминания значений, React.memo используется для переноса компонентов React Function для предотвращения повторного рендеринга. useCallback используется для запоминания функций.
Я не думаю, что вам нужно звонить
computedCallback = calcCallback();.computedCallbackдолжно быть просто = calcCallback, it will update the callback onceneverChange`изменения.