在 useEffect 中针对异步函数的 React Hook 警告:useEffect 函数必须返回一个清理函数或什么都不返回

新手上路,请多包涵

我正在尝试 useEffect 示例,如下所示:

useEffect(async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}, []);

我在控制台中收到此警告。但是我认为清理对于异步调用是可选的。我不确定为什么会收到此警告。以链接沙箱为例。 https://codesandbox.io/s/24rj871r0p

在此处输入图像描述

原文由 RedPandaz 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 966
2 个回答

我建议在 这里查看 Dan Abramov(React 核心维护者之一)的答案

我认为你让它变得比它需要的更复杂。

 function Example() {
 const [data, dataSet] = useState<any>(null)

 useEffect(() => {
 async function fetchMyAPI() {
 let response = await fetch('api/data')
 response = await response.json()
 dataSet(response)
 }

 fetchMyAPI()
 }, [])

 return <div>{JSON.stringify(data)}</div>
 }

从长远来看,我们将不鼓励这种模式,因为它鼓励竞争条件。例如 - 在您的通话开始和结束之间可能发生任何事情,并且您可以获得新的道具。相反,我们会推荐 Suspense 用于数据获取,它看起来更像

const response = MyAPIResource.read();

并且没有效果。但与此同时,您可以将异步内容移至单独的函数并调用它。

您可以 在此处阅读有关实验悬念的 更多信息。


如果你想在 eslint 之外使用函数。

 function OutsideUsageExample({ userId }) {
 const [data, dataSet] = useState<any>(null)

 const fetchMyAPI = useCallback(async () => {
 let response = await fetch('api/data/' + userId)
 response = await response.json()
 dataSet(response)
 }, [userId]) // if userId changes, useEffect will run again

 useEffect(() => {
 fetchMyAPI()
 }, [fetchMyAPI])

 return (
 <div>
 <div>data: {JSON.stringify(data)}</div>
 <div>
 <button onClick={fetchMyAPI}>manual fetch</button>
 </div>
 </div>
 )
 }


如果您使用 useCallback,请查看 useCallback 的工作原理示例。 沙盒

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

 export default function App() {
 const [counter, setCounter] = useState(1);

 // if counter is changed, than fn will be updated with new counter value
 const fn = useCallback(() => {
 setCounter(counter + 1);
 }, [counter]);

 // if counter is changed, than fn will not be updated and counter will be always 1 inside fn
 /*const fnBad = useCallback(() => {
 setCounter(counter + 1);
 }, []);*/

 // if fn or counter is changed, than useEffect will rerun
 useEffect(() => {
 if (!(counter % 2)) return; // this will stop the loop if counter is not even

 fn();
 }, [fn, counter]);

 // this will be infinite loop because fn is always changing with new counter value
 /*useEffect(() => {
 fn();
 }, [fn]);*/

 return (
 <div>
 <div>Counter is {counter}</div>
 <button onClick={fn}>add +1 count</button>
 </div>
 );
 }

原文由 ZiiMakc 发布,翻译遵循 CC BY-SA 4.0 许可协议

当你使用像这样的异步函数时

async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}

它返回一个 promise 并且 useEffect 不期望回调函数返回 Promise,而是期望不返回任何内容或返回一个函数。

作为警告的解决方法,您可以使用自调用异步函数。

 useEffect(() => {
    (async function() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    })();
}, []);

或者为了让它更清晰,你可以定义一个函数然后调用它

useEffect(() => {
    async function fetchData() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    };
    fetchData();
}, []);

第二种解决方案将使其更易于阅读,并将帮助您编写代码以在触发新请求时取消以前的请求或将最新的请求响应保存在状态中

工作代码和盒子

原文由 Shubham Khatri 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题