concepto
ejemplo
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
复制代码
definición
concepto
1. Devuelve una función de devolución de llamada memorizada .
2. Pase una función de devolución de llamada en línea y una matriz de dependencias como parámetros useCallback
, y devolverá una versión memorizada de la función de devolución de llamada, que solo se actualizará cuando cambie una dependencia. Es útil cuando pasa devoluciones de llamada a shouldComponentUpdate
componentes secundarios que están optimizados y usa la igualdad de referencia para evitar una representación innecesaria (por ejemplo).
useCallback(fn, deps)
equivalente a useMemo(() => fn, deps)
. documento en ingles
implementación del código fuente
Dos fases de llamada importantes de ganchos
Archivo fuente: ReactFiberHooks.js
Nota especial: la etapa de renderizado no se expande
const HooksDispatcherOnRerender: Dispatcher = {
useCallback: updateCallback,
// ...
};
复制代码
- La etapa HooksDispatcherOnMount corresponde al método de ganchos llamado cuando se inicializa la primera representación y corresponde a los
mountCallback
ganchos en espera, respectivamente. - La etapa HooksDispatcherOnUpdate corresponde a la etapa de actualización en la que la función setXXX activa la actualización y la nueva representación, correspondientes a los
updateCallback
ganchos en espera , respectivamente.
// 挂载阶段
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback, // useCallback 挂载(mount)
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
unstable_isNewReconciler: enableNewReconciler,
};
// 更新阶段
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback, // useCallback 更新(update)
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
unstable_isNewReconciler: enableNewReconciler,
};
复制代码
useCallback
dos etapas
// 挂载阶段
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 插入memoizedState属性
hook.memoizedState = [callback, nextDeps];
return callback;
}
// 更新阶段
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 相等直接返回旧的函数指针
return prevState[0];
}
}
}
// 有依赖更新则生成新的memoizedState;直接返回新的函数
hook.memoizedState = [callback, nextDeps];
return callback;
}
复制代码
fácil de malinterpretar
1. Si la función de devolución de llamada se vuelve a crear cuando se actualiza el componente
Conclusión: hay que recrear
Ejemplo
const DemoComponent = ()=>{
// 先创建了一个函数 funB
const callbackFunc = () => {};
// 函数 funB 当作第一个参数传入 useCallback 中;deps为[]
const funA = useCallback(callbackFunc, []);
// 省略代码...
}
复制代码
Conclusión: No es difícil ver que la callbackFunc
función se creará cada vez, en la etapa de 即useCallback不是为了减少函数创建次数
recombinación , se puede concluir que si funA se ha actualizado (si la corrección apunta ) depende de si hay un cambiouseCallback
update
callbackFunc
deps
2. Si todas las funciones están empaquetadas en una capauseCallback
useCallback no se utiliza
import { useState } from "react";
const SonComponent = ({ handleClick }) => {
return <button onClick={handleClick}>Click!</button>;
};
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((count) => count + 1);
};
return (
<div>
<p>{count}</p>
<SonComponent handleClick={handleClick} />
</div>
);
}
export default ParentComponent;
复制代码
Gráfico de rendimiento:
usar useCallback
import { useState, useCallback } from "react";
const SonComponent = ({ handleClick }) => {
return <button onClick={handleClick}>Click!</button>;
};
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((count) => count + 1);
},[]);
return (
<div>
<p>{count}</p>
<SonComponent handleClick={handleClick} />
</div>
);
}
export default ParentComponent;
复制代码
Gráfico de rendimiento:
Conclusión: a partir de la imagen de rendimiento, es mejor no agregar useCallback; razón: agregar useCallBack provocará una ejecución de función redundante.
Escenas a utilizar
1. Usar con React.memo
import { useState, useCallback, memo } from "react";
const SonExpenceComponent = memo(({ handleClick }) => {
return <button onClick={handleClick}>Click!</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>{count}</p>
<SonExpenceComponent handleClick={handleClick} />
</div>
);
}
export default ParentComponent;
复制代码
Hay un problema: cada vez que cambia el valor de conteo de deps, el componente SonExpenceComponent se actualiza
Solución 1: el esquema proporcionado en el sitio web oficial utiliza la combinación de useEffect y useRef
import { useState, useCallback, useRef, useEffect, memo } from "react";
const SonExpenceComponent = memo(({ handleClick }) => {
return <button onClick={handleClick}>Click!</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const countRef = useRef(0);
const handleClick = useCallback(() => {
setCount(countRef.current);
}, []);
// 同步countRef
useEffect(() => {
countRef.current = count;
}, [count]);
return (
<div>
<p>{count}</p>
<SonExpenceComponent handleClick={handleClick} />
</div>
);
}
export default ParentComponent;
复制代码
Solución 2: useMemoizedFn de ahooks
import { useMemo, useRef } from 'react';
type noop = (this: any, ...args: any[]) => any;
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>;
function useMemoizedFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn);
// 永远拿到最新fn
fnRef.current = useMemo(() => fn, [fn]);
const memoizedFn = useRef<PickFunction<T>>();
if (!memoizedFn.current) {
memoizedFn.current = function (this, ...args) {
return fnRef.current.apply(this, args);
};
}
return memoizedFn.current;
}
export default useMemoizedFn;
复制代码
Nota: Los anteriores son todos pseudocódigos y los subcomponentes predeterminados son componentes con una sobrecarga relativamente grande.
Desarrollar casos prácticos.
1. Representación de lista larga
Antes de la optimización:
import React, { useState} from "react";
const SonExpenceComponent =({ addCount }) => (
<div>
<button onClick={addCount}>Add</button>
{new Array(1000).fill("item").map((i, index) => (
<div>{i + index}</div>
))}
</div>
);
const App = () => {
const [count, setCount] = useState(0);
const addCount = () => {
setCount((count) => count + 1);
};
return (
<>
<p>Count: {count}</p>
<SonExpenceComponent addCount={addCount} />
</>
);
};
export default App;
复制代码
Optimizado:
import React, { useState, useCallback,memo } from "react";
const SonExpenceComponent =memo(({ addCount }) => (
<div>
<button onClick={addCount}>Add</button>
{new Array(1000).fill("item").map((i, index) => (
<div>{i + index}</div>
))}
</div>
));
const App = () => {
const [count, setCount] = useState(0);
const addCount = useCallback(() => {
setCount((count) => count + 1);
}, []);
return (
<>
<p>Count: {count}</p>
<SonExpenceComponent addCount={addCount} />
</>
);
};
export default App;
复制代码
Conclusión: después de agregar useCallback y memo, haga clic en el botón, puede ver que el tiempo de procesamiento general del componente de la aplicación es de 1 ms, que es mucho menor que el tiempo de procesamiento general de 11,8 ms sin agregar el tiempo de procesamiento general.
En conclusión
1. Se pueden usar componentes costosos con React.memo
2. A menos que haya una mejora significativa en el rendimiento, no hay necesidad de introduciruseCallback