【问题标题】:useeffect infinite loop even though state data is not changing即使状态数据没有改变,useeffect无限循环
【发布时间】:2022-08-18 16:58:25
【问题描述】:

每次我启动应用程序时,我的程序都会进入一个无限循环,不断调用 useEffect()。除了retrieveItemStatus() 函数之外,我有一个我认为不会改变的状态,所以我对为什么它会像现在这样进入循环感到困惑。

const App = () => {
  var items;
  const [itemStatuses, updateStatuses] = useState({});

  const retrieveItemStatus = async () => {
    var tempStatuses;
    try {
      const value = await AsyncStorage.getItem(\"@item_Statuses\");
      if (value !== null) {
        tempStatuses = await JSON.parse(value);
        //console.log(\"123456\");
      } else {
        tempStatuses = await JSON.parse(
          JSON.stringify(require(\"../default-statuses.json\"))
        );
      }
      updateStatuses(tempStatuses);
    } catch (error) {}
  };
  retrieveItemStatus();

  useEffect(() => {
    const copyData = async () => {
      const itemsCopy = [];

      const coll = await collection(db, \"Items\");
      const querySnapshots = await getDocs(coll);
      const docsArr = querySnapshots.docs;
      docsArr.map((doc) => {
        var data = doc.data();
        if (itemStatuses[data.name] === \"locked\") return;
        itemsCopy.push(data);
      });
      items = itemsCopy;
      //getItems([...itemsCopy]);
    };

    copyData();

  }, [itemStatuses]);

  return (
    <View style={styles.container}>
      <Text>temp.......</Text>
    </View>
  );
};
  • getItems 有什么作用?
  • 哎呀,应该注释掉。现在修好了。

标签: javascript react-native google-cloud-firestore react-hooks use-state


【解决方案1】:

它与useEffect 无关。你打电话给retrieveItemStatus无条件每次调用组件函数来渲染组件时。 retrieveItemStatus 调用 updateStatuses 其中改变状态.您会看到 useEffect 回调作为其副作用重复运行,因为您的 useEffect 回调将 itemStatuses 作为依赖项。

我假设您只需要itemStatuses 即可获取一次。如果是这样,请将调用放入带有空依赖数组的 useEffect 回调中:

useEffect(retrieveItemStatus, []);

另外,你有(注意***):

const App = () => {
  var items // ***
  // ...
  useEffect(() => {
    const copyData = async () => {
      // ...

      items = itemsCopy; // ***

      // ...
    };

    copyData();

  }, [itemStatuses]);
};

那是行不通的,当你从回调中分配给items 时,你可能试图用items 做的任何事情都已经使用了undefined(当你不给时它得到的值它一个)。如果您需要保留items,请将其置于状态(如果您将其用于渲染)或参考(如果您不使用)。


你在评论中说:

好的,所以我将 retrieveItemStatus() 调用放入 useEffect 并删除了修复循环的依赖项。但是现在有一个问题,在调用 copyData() 并且需要 itemStatuses 之前,itemStatuses 状态没有得到更新。所以它不会做任何事情,直到我再次手动刷新/渲染整个事情。

如果copyData 依赖于来自retrieveItemStatus 的结果,那么将对它们中的每一个的调用放在相同的useEffect,在您从 retrieveItemStatus 获得结果之前不要调用 copyData。类似于以下内容,尽管您当然需要对其进行调整,因为我没有所有详细信息(我还进行了一些其他的 cmets 并在其中进行了标记):

// *** There's no need to recreate this function on every render, just
// have it return the information
const retrieveItemStatus = async () => {
    try {
        let tempStatuses; // *** Declare variables in the innermost scope you can
        const value = await AsyncStorage.getItem("@item_Statuses");
        if (value !== null) {
            tempStatuses = await JSON.parse(value);
            //console.log("123456");
        } else {
            // *** stringify + parse isn't a good way to copy an object,
            // see your options at:
            // https://stackoverflow.com/questions/122102/
            tempStatuses = await JSON.parse(JSON.stringify(require("../default-statuses.json")));
        }
        return tempStatuses;
    } catch (error) {
        // *** Not even a `console.error` to tell you something went wrong?
    }
};

// *** Similarly, just pass `itemStatuses` into this function
const copyData = async (itemStatuses) => {
    const coll = await collection(db, "Items");
    const querySnapshots = await getDocs(coll);
    const docsArr = querySnapshots.docs;
    // *** Your previous code was using `map` just as a loop,
    // throwing away the array it creates. That's an anti-
    // pattern, see my post here:
    // https://thenewtoys.dev/blog/2021/04/17/misusing-map/
    // Instead, let's just use a loop:
    // (Alternatively, you could use `filter` to filter out
    // the locked items, and then `map` to build `itemsCopy`,
    // but that loops through twice rather than just once.)
    const itemsCopy = [];   // *** I moved this closer to where
                            // it's actually filled in
    for (const doc of docsArr) {
        const data = doc.data();
        if (itemStatuses[data.name] !== "locked") {
            itemsCopy.push(data);
        }
    }
    //getItems([...itemsCopy]); // *** ?
    return itemsCopy;
};

const App = () => {
    // *** A new `items` is created on each render, you can't just
    // assign to it. You have to make it a member of state (or use
    // a ref if it's not used for rendering.)
    const [items, setItems] = useState(null);
    const [itemStatuses, setItemStatuses] = useState({});
    // ***               ^−−−−− the standard convention is `setXyz`

    useEffect(() => {
        (async () => {
            const newStatuses = await retrieveItemStatus();
            const newItems = copyData(newStatuses);
            // *** Do you need `itemStatuses` to be in state at all? If it's
            // only used for calling `copyData`, there's no need.
            setItemStatuses(newStatuses);
            setItems(newItems);
        })().catch((error) => {
            console.error(error);
        });
    }, []);

    // *** You didn't show what you're using here, so it's hard to be
    // sure what needs to be in state and what doesn't.
    // Only put `items` or `itemStatuses` in state if you use them for
    // rendering.
    return (
        <View style={styles.container}>
            <Text>temp.......</Text>
        </View>
    );
};

以下是这些链接作为链接:

【讨论】:

  • 好的,所以我将retrieveItemStatus() 调用放入useEffect 并删除了修复循环的依赖项。但是现在存在一个问题,即在调用 copyData() 并且需要 itemStatuses 之前 itemStatuses 状态没有得到更新。所以它不会做任何事情,直到我再次手动刷新/渲染整个事情。
  • @skushu - 如果两者之间存在依赖关系,请在相同的useEffect 回调中执行它们,等到retrieveItemStatus 完成后再调用copyData。我已经更新了答案以显示这样做并标记了我看到的其他一些问题。
【解决方案2】:

@T.J。克劳德的回答是正确的,但我想澄清一点,这样你就明白为什么你的 useEffect 没有停止运行。

如您所知,当依赖数组中的依赖发生变化时,useEffect 就会运行。但是您 itemStatuses 中的数据没有正确更改(根据您的说法)?那么为什么 useEffect 会重新运行呢?让我们看看下面的例子:

const obj1 = {};
const obj2 = {};
const obj3 = {a:2};
const obj4 = {a:2};


console.log(obj1 === obj2)
console.log(obj3 === obj4)

console.log(obj3.a === obj4.a)

如您所见,javascript 并不认为空对象严格等于另一个空对象。这是因为这些对象无论其内容如何都引用内存中的不同位置。

这就是为什么每次retrieveItemStatus 运行时,它都会更新itemStatuses。然后因为 itemStatuses 得到了更新(尽管值相同),useEffect 触发了重新渲染,所以一切又重新开始了。

【讨论】:

    【解决方案3】:

    主要原因是在没有任何事件的情况下调用retrieveItemStatus();。 如果你想在页面加载时调用这个函数,你应该喜欢这个。

    ...
    useEffect(() => {
      retrieveItemStatus()
    }, [])
    ...
    

    这将解决循环问题。 :)

    【讨论】:

    • 好的,所以我将retrieveItemStatus() 调用放入useEffect 并删除了修复循环的依赖项。但是现在存在一个问题,即在调用 copyData() 并且需要 itemStatuses 之前 itemStatuses 状态没有得到更新。所以它不会做任何事情,直到我再次手动刷新/渲染整个事情。
    猜你喜欢
    • 2021-11-06
    • 2018-06-09
    • 2019-07-22
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-22
    • 2019-10-28
    相关资源
    最近更新 更多