【问题标题】:Trouble with simple example of React Hooks useCallbackReact Hooks useCallback 的简单示例的问题
【发布时间】:2023-03-08 11:15:01
【问题描述】:

我正在尝试制作一个简单的示例,该示例遵循页面 https://reactjs.org/docs/hooks-reference.html#usecallback 文档中的 React Hooks 示例

没有useCallback,代码在这个例子中可以找到:

import React, { useCallback } from "react";

function Test(props) {
  function doSomething(a, b) {
    console.log("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.log("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;

问题代码在这里:

https://stackblitz.com/edit/react-usememo2?file=Hello.js

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    useCallback 的目的是能够利用当前范围内的道具或状态,并且在重新渲染时可能会发生变化。当你需要新版本的回调时,依赖数组会告诉 React。如果你想记住一个昂贵的计算,你需要使用useMemo

    下面的示例演示了useCallbackuseMemo 之间的区别以及不使用它们的后果。在这个例子中,我使用React.memo 来防止Child 重新渲染,除非它的道具或状态发生变化。这样可以看到useCallback 的好处。现在如果Child 收到一个新的onClick 道具,它将导致重新渲染。

    子 1 正在接收一个非记忆的 onClick 回调,因此每当父组件重新渲染时,子 1 总是会收到一个新的 onClick 函数,因此它会被强制重新渲染。

    孩子 2 正在使用从 useCallback 返回的记忆化的 onClick 回调,而孩子 3 正在使用通过 useMemo 的等价物来演示

    useCallback(fn, inputs) 等价于 useMemo(() => fn, inputs)

    对于子 2 和 3,每次单击子 2 或 3 时仍会执行回调,useCallback 只是确保在依赖项未更改时传递相同版本的 onClick 函数。

    显示的以下部分有助于指出正在发生的事情:

    nonMemoizedCallback === memoizedCallback: false|true

    另外,我正在显示somethingExpensiveBasedOnA 和使用useMemo 的记忆版本。出于演示目的,我使用了一个不正确的依赖数组(我故意省略了b),以便您可以看到当b 更改时记忆化版本不会更改,但当a 更改时它确实会更改。每当ab 更改时,非记忆版本就会更改。

    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);
    
    

    这是一个相关的答案:React Hooks useCallback causes child to re-render

    【讨论】:

    • 在第一个示例中,它仍然输出了 3 次 console.log。我没有得到什么? (我猜是个难题)
    • 你只是误解了useCallback的目的。 useCallback 不会记住函数的结果——它会记住函数本身。在第一个示例中,没有充分的理由使用它,但在第二个示例中,它将阻止 ChildonClick 属性在重新渲染时发生变化。如果你想记住结果(以防止重复执行昂贵的函数),你应该使用useMemo
    • 你说的太对了,我不明白。我被文档中的这条评论所困扰:useCallback(fn, inputs) is equivalent to useMemo(() =&gt; fn, inputs). 我正在把课件放在一起,有点迷失,寻找好的例子。
    • 我已将我的示例替换为不再基于您的代码的示例,但如果您花足够的时间研究它、与之交互并查看结果,这应该有助于解决问题。
    猜你喜欢
    • 1970-01-01
    • 2021-04-15
    • 1970-01-01
    • 2019-02-27
    • 1970-01-01
    • 2020-11-01
    • 2020-04-26
    • 2021-10-18
    • 1970-01-01
    相关资源
    最近更新 更多