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


当前回答

其他答案已经有很多例子给出了,并且解释得很清楚,所以我将从TypeScript类型定义的角度来解释它们。

useEffect钩子TypeScript签名:

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

效果类型:

// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);

// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };

现在我们应该知道为什么effect不能是一个异步函数了。

useEffect(async () => {
  //...
}, [])

async函数将返回一个带有隐式未定义值的JS promise。这不是useEffect的期望。

其他回答

忽略警告,并使用useEffect钩子和一个异步函数,就像这样:

import { useEffect, useState } from "react";

function MyComponent({ objId }) {
  const [data, setData] = useState();

  useEffect(() => {
    if (objId === null || objId === undefined) {
      return;
    }

    async function retrieveObjectData() {
      const response = await fetch(`path/to/api/objects/${objId}/`);
      const jsonData = response.json();
      setData(jsonData);
    }
    retrieveObjectData();

  }, [objId]);

  if (objId === null || objId === undefined) {
    return (<span>Object ID needs to be set</span>);
  }

  if (data) {
    return (<span>Object ID is {objId}, data is {data}</span>);
  }

  return (<span>Loading...</span>);
}

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

import { useEffect } from 'react';


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

现在你可以传递一个async函数:

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

更新

如果您选择这种方法,请注意这是一种糟糕的形式。当我知道这是安全的时候,我就会这样做,但这总是糟糕的方式和随意的。

悬疑的数据获取,仍处于实验阶段,将解决一些情况。

在其他情况下,您可以将异步结果建模为事件,以便您可以根据组件生命周期添加或删除侦听器。

或者你可以将异步结果建模为一个Observable,这样你就可以根据组件的生命周期订阅和取消订阅。

对于使用React Hooks从外部API中获取,你应该调用一个从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的依赖数组([])是空的,这意味着它的行为就像React类组件中的componentDidMount一样,它只会在组件被挂载时执行一次。

对于加载文本,你可以使用React的条件渲染来验证你的帖子是否为空,如果是,渲染一个加载文本,否则,显示帖子。当你完成从API获取数据并且post不为空时,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

对于React版本<=17

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

对于React版本>=18

从React 18开始,你也可以使用悬念,但如果你没有使用正确实现它的框架,还不建议使用它:

在React 18中,你可以开始在Relay、Next.js、Hydrogen或Remix等自以为是的框架中使用悬念来获取数据。从技术上讲,使用悬疑技术获取临时数据是可行的,但仍然不推荐作为一般策略。

如果不是框架的一部分,您可以尝试一些库来实现它,比如swr。


这是一个非常简单的悬念例子。你需要为悬疑抛出一个承诺来捕获它,首先显示回退组件,并在承诺被解决时呈现Main组件。

let fullfilled = false;
let promise;

const fetchData = () => {
  if (!fullfilled) {
    if (!promise) {
      promise = new Promise(async (resolve) => {
        const res = await fetch('api/data')
        const data = await res.json()

        fullfilled = true
        resolve(data)
      });
    }

    throw promise
  }
};

const Main = () => {
  fetchData();
  return <div>Loaded</div>;
};

const App = () => (
  <Suspense fallback={"Loading..."}>
    <Main />
  </Suspense>
);

你也可以使用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>
}