React Hook 警告正在使用的异步函数Effect:useEffect 函数必须返回清理函数或什么都不返回

2022-08-29 23:18:37

我正在尝试下面的例子: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


答案 1

我建议看看 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>
}

从长远来看,我们将不鼓励这种模式,因为它会鼓励竞争条件。例如 - 在你的通话开始和结束之间可能发生任何事情,你本来可以得到新的道具。相反,我们将建议悬念进行数据获取,这看起来更像

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>
  );
}

答案 2

当您使用异步函数时,例如

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,并且不希望回调函数返回 Promise,而是期望不返回任何内容或返回函数。useEffect

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

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

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

工作代码和框