为什么钩子中的函数依赖性不会导致无限渲染

Why doesn't a function dependency in hooks cause infinite renders

以下是片段 link to codesandbox:

// function getFetchUrl(query) {
//   return "https://hn.algolia.com/api/v1/search?query=" + query;
// }
function App() {
  const [reactResult, setReactResult] = useState(null);
  const [reduxResult, setReduxResult] = useState(null);
  function SearchResults() {
    //  Re-triggers all effects on every render
    // const getFetchUrl = useCallback((query) =>  {
      // return "https://hn.algolia.com/api/v1/search?query=" + query;
    // }, []);


     function getFetchUrl(query) {
       return "https://hn.algolia.com/api/v1/search?query=" + query;
     }

    useEffect(() => {
      console.log("running effect: 15");
      setReactResult(getFetchUrl("react"));
      // ... Fetch data and do something ...
      // }, [getFetchUrl]); //  Deps are correct but they change too often
    }, [getFetchUrl]);

    useEffect(() => {
      console.log("running effect: 21");
      setReduxResult(getFetchUrl("redux"));
      // ... Fetch data and do something ...
      // }, [getFetchUrl]); //  Deps are correct but they change too often
    }, [getFetchUrl]);

    // ...
  }

  SearchResults();

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>{reactResult}</h2>
      <h2>{reduxResult}</h2>
    </div>
  );
}

控制台的输出是

running effect: 15
running effect: 21
running effect: 15
running effect: 21

我已经检查了 的答案,我了解到函数被重新定义,这导致 useEffect 再次变为 运行(第二次)。但我想澄清一个疑问:

useEffect 运行 第二次调用时,它调用 stateSetter 函数(要求 React 再次渲染组件)。

所以上面的代码片段 运行 不应该在无限循环中吗?

示例和基本理解来自 A Complete Guide to useEffect

当使用 useState 时,反应足够聪明,可以跳过 re-rendering 如果状态值 尽管调用了 setState 函数,但实际上并没有改变。 (这记录在 https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update

您提供的示例代码与链接的代码笔略有不同,实际上只会产生一条 "set" 控制台日志消息。

running effect: 15
running effect: 21

事件的顺序是:

  1. 初始渲染,触发两种效果并更新 reactResultreduxResult 状态。这会使 re-render.
  2. 排队
  3. 组件re-renders。在您包含的示例中,您使用的 useCallback 没有任何依赖关系,这将 return 先前的值,因此效果不会 运行.

另一方面,在您的代码笔中,您在每次执行时重新定义回调,而不是 useCallback,在这种情况下,您将获得两条 "sets" 控制台消息:

  1. 初始渲染,触发两种效果并更新 reactResultreduxResult 状态。这会使 re-render.
  2. 排队
  3. 组件re-renders。 getFetchUrl 是局部函数,因此 等于 getFetchUrl 来自之前的 运行。因此,效果将 re-run。但是 setReactResultsetReduxResult 都使用与之前相同的值调用,因此 re-render 将 而不是 被触发。

您的效果仅在 getFetchUrl 更改时触发...并且由于它是一个记忆回调(不会更改)效果仅 运行 一次。