对useEffect中的异步函数进行React Hook警告:useEffect函数必须返回清除函数或不返回任何内容

时间:2018-11-16 06:07:13

标签: javascript reactjs react-hooks

我正在尝试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 enter image description here

12 个答案:

答案 0 :(得分:24)

我建议看看Dan Abramov (one of the react creators) answer here

  

我认为您正在使它变得比所需的更加复杂。

export default function Example() { 
    const [data, dataSet] = useState(false)

    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await res.json()
      console.log(response);
      dataSet(response)
    }

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

  return <div>{data}</div>
}
  

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

const response = MyAPIResource.read();
  

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

答案 1 :(得分:23)

当您使用类似异步功能的

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();
}, []);

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

Working codesandbox

答案 2 :(得分:15)

在React提供更好的方法之前,您可以创建一个帮助器useEffectAsync.js

import { useEffect } from 'react';


export default function useEffectAsync(effect, inputs) {
    useEffect(() => {
        effect();
    }, inputs);
}

现在您可以传递异步功能了:

useEffectAsync(async () => {
    const items = await fetchSomeItems();
    console.log(items);
}, []);

答案 3 :(得分:6)

void operator可以在这里使用。
代替:

React.useEffect(() => {
    async function fetchData() {
    }
    fetchData();
}, []);

React.useEffect(() => {
    (async function fetchData() {
    })()
}, []);

您可以写:

React.useEffect(() => {
    void async function fetchData() {
    }();
}, []);

它更干净,更漂亮。


异步影响可能会导致内存泄漏,因此对组件卸载执行清理非常重要。在获取的情况下,可能看起来像这样:

function App() {
  const [data, setData] = React.useState([])

  React.useEffect(() => {
    const abortController = new AbortController()
    ;(async function fetchData() {
      try {
        const url = "https://jsonplaceholder.typicode.com/todos/1"
        const response = await fetch(url, {
          signal: abortController.signal,
        })
        setData(await response.json())
      } catch (error) {
        console.log("error", error)
      }
    })()
    return abortController.abort // cancel pending fetch request on component unmount
  }, [])

  return <pre>{JSON.stringify(data, null, 2)}</pre>
}

答案 4 :(得分:5)

我通读了这个问题,并认为答案中没有提到实现useEffect的最佳方法。 假设您有一个网络通话,并且希望在收到回复后立即采取措施。 为了简单起见,让我们将网络响应存储在状态变量中。 可能要使用动作/还原器通过网络响应来更新商店。

const [data, setData] = useState(null);

/* This would be called on initial page load */
useEffect(()=>{
    fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(data => {
        setData(data);
    })
    .catch(err => {
        /* perform error handling if desired */
    });
}, [])

/* This would be called when store/state data is updated */
useEffect(()=>{
    if (data) {
        setPosts(data.children.map(it => {
            /* do what you want */
        }));
    }
}, [data]);

参考=> https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

答案 5 :(得分:5)

对于其他读者来说,该错误可能是由于没有括号包装异步函数的事实造成的:

考虑异步函数initData

  async function initData() {
  }

此代码将导致您的错误:

  useEffect(() => initData(), []);

但是,这不会:

  useEffect(() => { initData(); }, []);

(注意initData()的括号

答案 6 :(得分:4)

要使用 API 从外部 React Hooks 获取数据,您应该调用一个从 useEffect 钩子内部的 API 获取数据的函数。

像这样:

async function fetchData() {
    const res = await fetch("https://swapi.co/api/planets/4/");
    res
      .json()
      .then(res => setPosts(res))
      .catch(err => setErrors(err));
  }

useEffect(() => {
    fetchData();
}, []);

我强烈建议您不要在 useEffect 钩子内定义您的查询,因为它将被无限次重新渲染。并且由于您无法使 useEffect 异步,因此您可以将其内部的函数设为异步。

在上面显示的示例中,API 调用位于另一个分离的异步函数中,因此它确保调用是异步的并且只发生一次。此外,useEffect's 依赖数组([])是空的,这意味着它的行为就像 React 类组件中的 componentDidMount,它只会在组件被挂载时执行一次。

对于加载文本,您可以使用 React 的条件渲染来验证您的帖子是否为空,如果是,则渲染加载文本,否则,显示帖子。当您完成从 API 获取数据并且帖子不为空时,else 将为真。

{posts === null ? <p> Loading... </p> 
: posts.map((post) => (
    <Link key={post._id} to={`/blog/${post.slug.current}`}>
      <img src={post.mainImage.asset.url} alt={post.mainImage.alt} />
      <h2>{post.title}</h2>
   </Link>
))}

我看到你已经在使用条件渲染,所以我建议你深入研究它,特别是验证对象是否为空!

如果您需要有关使用 Hooks 使用 API 的更多信息,我建议您阅读以下文章。

https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd

https://reactjs.org/docs/conditional-rendering.html

答案 7 :(得分:1)

尝试

const MyFunctionnalComponent: React.FC = props => {
  useEffect(() => {
    // Using an IIFE
    (async function anyNameFunction() {
      await loadContent();
    })();
  }, []);
  return <div></div>;
};

答案 8 :(得分:0)

请试试这个

 useEffect(() => {
        (async () => {
          const products = await api.index()
          setFilteredProducts(products)
          setProducts(products)
        })()
      }, [])

答案 9 :(得分:0)

使用自定义 library 提供的 useAsyncEffect 钩子,安全地执行异步代码和在效果内部发出请求变得微不足道,因为它使您的代码可自动取消(这只是功能列表中的一件事)。查看Live Demo with JSON fetching

import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch";

/*
 Notice: the related network request will also be aborted
 Checkout your network console
 */

function TestComponent(props) {
  const [cancel, done, result, err] = useAsyncEffect(
    function* () {
      const response = yield cpFetch(props.url).timeout(props.timeout);
      return yield response.json();
    },
    { states: true, deps: [props.url] }
  );

  return (
    <div className="component">
      <div className="caption">useAsyncEffect demo:</div>
      <div>
        {done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
      </div>
      <button className="btn btn-warning" onClick={cancel} disabled={done}>
        Cancel async effect
      </button>
    </div>
  );
}

export default TestComponent;

The same demo using axios

答案 10 :(得分:0)

将其包装成 useCallback 并将其用作 useEffect 钩子的依赖项。

const getPosts = useCallback(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);
    }
}, []);

useEffect(async () => {
    getPosts();
}, [getPosts]);

答案 11 :(得分:0)

您也可以使用 IIFE 格式来保持简短

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

    useEffect(() => {
        (async () => {
            let response = await fetch('api/data')
            response = await response.json()
            dataSet(response);
        })();
    }, [])

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