【问题标题】:useEffect doesn't run after rendering渲染后 useEffect 不运行
【发布时间】:2022-01-30 15:43:05
【问题描述】:

我对 useEffect 是如何触发的以及它是如何工作的感到有点困惑。我写了一个这样的函数,但是 useEffect 根本没有运行。我想从 API 中获取数据,然后根据数据呈现页面。但它不会触发 useEffect。如果我不使用useEffect,它会渲染页面3次。

async function getData() {
  var tmpArrData = [];
  await fetch("this API is hidden due to the privacy of the company - sorry")
    .then((res) => res.json())
    .then((data) => {
      console.log("data", data);
      tmpArrData = data;
  });
  console.log("tmpData ", tmpArrData);
  return tmpArrData;
}

function App() {

  const [arrData, setArrData] = useState();
  const [loadData, setLoadData] = useState(false);

  useEffect(() => {
    console.log("if it works, this line should be shown");
    const tmpArrData = getData();
    setArrData(tmpArrData);
  }, [arrData]);


  const data = arrData[0];
  console.log(data);

  return (
      <GifCompoment 
      id = {data.id}
      name = {data.name}
      activeTimeTo = {data.activeTimeTo}
      activeTimeFrom = {data.activeTimeFrom}
      requiredPoints = {data.requiredPoints}
      imageUrl = {data.imageUrl}
      />
  );
}

export default App;

【问题讨论】:

  • useEffect 钩子保证在初始渲染结束时至少运行一次。你是说你看不到console.log("if it works, this line should be shown"); 日志吗?
  • 是的,我没看到那行

标签: reactjs async-await react-hooks use-effect use-state


【解决方案1】:

useEffect 挂钩保证在初始渲染结束时至少运行一次。

getData 是一个 async 函数,useEffect 回调代码不等待它解决。简单的解决方案是从 getData 隐式返回的 Promise 链接并访问解析的值以更新 arrData 状态。确保从 useEffect 的依赖数组中移除状态,这样就不会创建渲染循环。

getData 实现可以通过返回 fetch 结果来清理/收紧,无需先保存到临时变量中。

async function getData() {
  return await fetch(".....")
    .then((res) => res.json());
}

useEffect(() => {
  console.log("if it works, this line should be shown");
  getData().then((data) => {
    setArrData(data);
  });
}, []); // <-- empty dependency so effect called once on mount

另外,由于arrData 最初是未定义的,arrData[0] 可能会抛出错误。您可能希望提供有效的初始状态,并在第一个元素未定义的情况下提供后备值,因此您不要尝试访问未定义对象的属性。

const [arrData, setArrData] = useState([]);

...

const data = arrData[0] || {}; // data is at least an object

return (
  <GifCompoment 
    id={data.id}
    name={data.name}
    activeTimeTo={data.activeTimeTo}
    activeTimeFrom={data.activeTimeFrom}
    requiredPoints={data.requiredPoints}
    imageUrl={data.imageUrl}
  />
);

【讨论】:

  • 我认为这会导致获取数据的无限循环
  • @cEeNiKc 是的,很棒的收获...我仍在最终确定/清理我的答案。
【解决方案2】:

你应该调用Promise的状态设置器

function App() {
    const [arrData, setArrData] = useState();

    function getData() {
        fetch("/api/hidden")
            .then((res) => res.json())
            .then((data) => setArrData(data));
    }

    useEffect(() => {
        console.log("if it works, this line should be shown");
        getData();
    }, []);

    return ...
}

【讨论】:

    【解决方案3】:

    通过结合 Drew Reese 和 Artyom Vancyan 的答案,我已经解决了我的问题。我觉得关键点在then函数.then((data) =&gt; setArrData(data))中的setState,不要把依赖放在useEffect中,在useEffect里面等待。非常感谢超级超人。大爱

    useEffect(() => {
        console.log("if it works, this line should be shown");
        const getData = async () => {
          await fetch("hidden API")
          .then((ref) => ref.json())
          .then((data) => {
            setArrData(data);
          });
        }
        getData();
      }, []);
    

    【讨论】:

    • 其次,控制台日志(如果有效,应显示此行)不起作用,因为渲染时出错。当我解决错误时,显示该行
    【解决方案4】:
    function App() {
    
      const [arrData, setArrData] = useState([]);
      const [loadData, setLoadData] = useState(false);
    
      
      const async getData=()=> {
      var tmpArrData = [];
      await fetch("this API is hidden due to the privacy of the company - sorry")
        .then((res) => res.json())
        .then((data) => {
          console.log("data", data);
          setArrData(tmpArrData);
      });
      console.log("tmpData ", tmpArrData);
      return tmpArrData;
    }
    
      useEffect(() => {
        console.log("if it works, this line should be shown");
        const callApi =async()=>{
          await getData();
        } 
      }, [arrData]);
    
    
      const data = arrData[0];
      console.log(data);
    
      return (
          <GifCompoment 
          id = {data.id}
          name = {data.name}
          activeTimeTo = {data.activeTimeTo}
          activeTimeFrom = {data.activeTimeFrom}
          requiredPoints = {data.requiredPoints}
          imageUrl = {data.imageUrl}
          />
      );
    }
    
    export default App;
    

    页面将呈现正常的三到四倍。

    【讨论】:

    • "页面将呈现正常的三到四倍。"这根本不正常,并且会随着您的扩展而导致性能问题。
    • *.com/questions/48846289/… 这样可能会有所帮助
    • 这篇文章与问题无关。重新渲染的数量将始终取决于组件的状态和道具的更新。如果您的组件在您只想获取一次数据时重新渲染 3-4 次,则该组件存在一些问题。假设页面渲染 3-4 次是正常的,这不是您应该追求的,而是真正寻找问题所在。
    • 您提出的解决方案也会导致无限循环
    • useEffect 的 deps of [] 所以这只发生在第一次渲染上。然后您将更改状态 2 次,因此会发生重新渲染。这并不意味着 DOM 改变了 3 次。