【问题标题】:React Hooks Exhaustive-deps async infinite LoopReact Hooks Exhaustive-deps 异步无限循环
【发布时间】:2020-11-20 07:18:14
【问题描述】:

我有以下组件:

import React, { useState, useEffect } from "react";

const App = () => {
    const [data, setData] = useState<null | any[]>(null);
    const [checked, setChecked] = useState(false);
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        setLoading(true);

        (async () => {
            if (data) {
                // Could do something else here if data already exsisted
                console.log("Data exists already");
            }

            const ret = await fetch("https://jsonplaceholder.typicode.com/users?delay=1000", { cache: "no-store" });
            const json = await ret.json();
            setData(json);
            setLoading(false);
        })();

    }, [checked]);

    return (
        <>
            <h1>useEffect Testing {loading && " ⌛"}</h1>
            <hr />
            <input type="checkbox" checked={checked} onChange={(e) => setChecked(e.target.checked)} />
            <pre style={{ fontSize: "0.8em" }}>{JSON.stringify(data)}</pre>
        </>
    );
};

export default App;

目前,我的if(data) 毫无意义,但是如果我想检查当前的data 状态,然后根据状态运行异步函数 eslint(react-hooks/exhaustive-deps) 告诉我钩子缺少 data 依赖项,它就是这样。当我将数据添加到导致无限循环的依赖项列表时,问题就出现了。

知道如何解决这个问题吗?感觉它应该是相当简单的模式,但是我发现所有建议都使用扩展的 setState(prev =&gt; prev + 1) 重载,在这种情况下对我没有帮助。

【问题讨论】:

  • 忽略 linter。这是一个warning,而不是error,如果你在useEffect hook 中改变了一个依赖,它会永远循环下去。抱歉,如果我听起来很沮丧,这不是你的错,我们会收到很多这样的问题。详尽的 deps linter 规则通常是误报,除非不是这样,这就是 React 团队无法摆脱它的原因。
  • @JaredSmith 坏主意,当useEffect 在其关闭时记住错误的data 时可能会出现这种情况
  • @JaredSmith 当然,console.log("Data exists already"); return; 如果忽略它,为什么还要使用 linter?
  • @NikitaMadeev 抱歉,也许我不清楚:我建议 OP 在遵循其建议时忽略 linter 在这种特定情况下会创建一个无限循环,而不是一般情况下。
  • 一个好主意是不要在这种方法中完全接触数据。如果OP要检查,最好在启动效果前检查

标签: reactjs typescript react-hooks eslint-plugin-react-hooks


【解决方案1】:

您正在设置data 并在效果中读取它。除非您手动停止它,否则这将导致您陷入无限循环。以下是一些解决方案。

如果有数据则返回而不是修改它:

useEffect(() => {
    setLoading(true);

    (async () => {
        if (data) {
            // Could do something else here if data already exsisted
            console.log("Data exists already");
            return;
        }

        const ret = await fetch("https://jsonplaceholder.typicode.com/users?delay=1000", { cache: "no-store" });
        const json = await ret.json();
        setData(json);
        setLoading(false);
    })();

}, [checked, data]);

在设置它的效果中消除您对data 的依赖(我会做什么):

useEffect(() => {
    setLoading(true);

    (async () => {
        const ret = await fetch("https://jsonplaceholder.typicode.com/users?delay=1000", { cache: "no-store" });
        const json = await ret.json();
        setData(json);
        setLoading(false);
    })();

}, [checked]);

useEffect(() => {
        if (data) {
            // Could do something else here if data already exsisted
            console.log("Data changed and exists!");
        }
}, [data]);

或者您可以手动执行一些停止无限循环的操作。

useEffect(() => {
    if (loading || data) {
      console.log('Do something with loading or data, but do not modify it!')
      return;
    }

    setLoading(true);

    (async () => {
        const ret = await fetch("https://jsonplaceholder.typicode.com/users?delay=1000", { cache: "no-store" });
        const json = await ret.json();
        setData(json);
        setLoading(false);
    })();
}, [checked, data]);

【讨论】:

  • 这两个useEffects正是我所需要的!我附加的代码是我实际所做的非常简化的版本,但是分离这两个效果允许我检查和执行更改而不会导致无限循环。
  • 是的,分离关注点是 React 的一个原则,它可以过渡到 useEffect。如果可行,请将此标记为已接受!很高兴它有帮助。