Reactjs 自定义挂钩导致无限循环

Reactjs Custom hook causing an infinite loop

谁能帮忙解释一下为什么这段代码会导致无限循环,请问修复它的最佳方法是什么,谢谢。
我假设它是因为 App 组件中的 useEffect 导致重新渲染,然后 useState 也会导致渲染,从而导致无限循环。我想我不明白 useEffect 和 useState 是如何正常工作的。

import "./styles.css";
import React, { useEffect, useState } from "react";

interface IObject {
  isLoading: boolean;
  isError: boolean;
  data: any[] | any;
}

function useHook1(): IObject {
  console.log("hook 1 too many re-renders?");
  return { isLoading: false, isError: false, data: [] };
}

function useHook2(): IObject {
  const result = { isLoading: false, isError: false, data: "testing" };
  console.log("hook 2 too many re-renders?");
  return result;
}

export default function App() {
  const { isLoading, isError, data } = useHook1();
  const testResult = useHook2();
  const [state, setState] = useState();

  useEffect(() => {
    console.log("inside useEffect within App")
    setState(testResult.data)
  }, [testResult])

  console.log("too many re-renders?");
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <p>{testing}</p>
    </div>
  );
}

useHook2 return每次运行都会生成一个新对象。这意味着 testResult 在每次渲染时都是 new/changed,并且只要 testResult 发生变化,您的 useEffect 就会运行。所以:

  1. 您的效果会更新状态,这会导致 re-render。

  2. 在重新渲染时,useHook2 被调用并且 testResult 被更新。

  3. testResult 改变了,所以你的效果再次运行,你 return 到第 1 步,进入无限循环。


如果我明白你想做什么,解决方案是在自定义挂钩中进行状态管理:

function useHook1() {
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const [data, setData] = useState([])

  return {
    isLoading,
    isError,
    data,
  };
}

您可以通过将重复的 useState 用法折叠成一个 useReducer:

来稍微简化一下
function useHook1() {
  const [state, dispatch] = useReducer(
    (state, action) => ({...state, ...action}),
    { isLoading: false, isError: false, data: [] }
  );

  return { ...state };
}

如果您需要从挂钩外部触发更新,您也可以使其可用:

function useHook1() {
  const [state, dispatch] = useReducer(
    (state, action) => ({...state, ...action}),
    { isLoading: false, isError: false, data: [] }
  );

  return { ...state, dispatch };
}

export default function App () {
  const [{ isLoading, isError, data }, setState] = useHook1();
  
  return isLoading ? <div>Loading</div> : (
    <div onClick={() => dispatch({ data: ["new data"]})}>...</div>
  )
}