useCallback comprensión y aplicación

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  shouldComponentUpdatecomponentes 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,
  // ...
};
复制代码
  1. La etapa HooksDispatcherOnMount corresponde al método de ganchos llamado cuando se inicializa la primera representación y corresponde a los mountCallbackganchos en espera, respectivamente.
  2. 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 updateCallbackganchos 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,
};
复制代码

useCallbackdos 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 callbackFuncfunció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 cambiouseCallbackupdatecallbackFuncdeps

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:
imagen.png

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:
imagen.png
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;
复制代码

imagen.pngOptimizado:

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;
复制代码

imagen.pngConclusió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

El principio de usoMemo

Supongo que te gusta

Origin juejin.im/post/7079663951214018596
Recomendado
Clasificación