如何在 React 的 UseEffect() 中调用异步函数?

Posted

技术标签:

【中文标题】如何在 React 的 UseEffect() 中调用异步函数?【英文标题】:How to call an async function inside a UseEffect() in React? 【发布时间】:2019-11-12 06:30:07 【问题描述】:

我想调用一个异步函数并为我的 UseEffect 获取结果。

我在网上找到的 fetch api 示例是直接在 useEffect 函数中制作的。 如果我的 URL 发生变化,我必须修补我的所有获取。

当我尝试时,我收到一条错误消息。

这是我的代码。


    async function getData(userId) 
        const data = await axios.get(`http://url/api/data/$userId`)
            .then(promise => 
                return promise.data;
            )
            .catch(e => 
                console.error(e);
            )
            return data;
    


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

        useEffect(async () => 
            setData(getData(1))
        , []);

        return (
            <div>
                this is the data["name"]
            </div>
        );
    

index.js:1375 警告:效果函数不能返回除用于清理的函数之外的任何内容。 看起来你写了 useEffect(async () => ...) 或者返回了一个 Promise。相反,在你的效果中编写异步函数并立即调用它:

useEffect(() => 
  async function fetchData() 
    // You can await here
    const response = await MyAPI.getData(someId);
    // ...
  
  fetchData();
, [someId]); // Or [] if effect doesn't need props or state

【问题讨论】:

如果一个函数返回一个promise,你可以await或者.then(...),不能同时使用。 这回答了我猜想的问题,但这并没有真正解决问题。你实际上是在开始一个可能随时结束的承诺。如果您的异步作业与组件的生命周期无关,那很好。但否则,您将首先遇到麻烦并且难以调试渲染错误。我对 React 的经验不够肯定,但我觉得这也与 React 的依赖系统混淆了。 另见React Hook Warnings for async function in useEffect 【参考方案1】:

在效果中创建一个异步函数,等待getData(1) 结果然后调用setData()

useEffect(() => 
  const fetchData = async () => 
     const data = await getData(1);
     setData(data);
  

  fetchData();
, []);

【讨论】:

为什么这与在 useEffect 挂钩之外定义异步函数有什么不同? @LelandReardon 如果你想在 useEffect 钩子之外定义异步函数,你必须将它添加到 useEffect 的依赖列表中,并将其定义包装到 useCallback 中必要的依赖项以防止不必要的调用,有关更多信息,请查看反应文档here【参考方案2】:

如果您要立即调用它,您可能希望将其用作匿名函数:

useEffect(() => 

  (async () => 
     const data = await getData(1);
     setData(data);
  )();

, []);

【讨论】:

有什么好处? @Remi 该函数将被立即调用,不会在其他任何地方使用,因此不需要名称或任何预定义 如果组件被卸载,如何取消请求? @Remi 这与 OP 的问题无关,您可以在单独的线程上提出这个问题,因为它可能有不同的实现,其中大部分与您使用匿名异步还是预定义的无关用于获取数据 边缘案例。如果你没有取消它,那么 React 将设置一个状态,即使这个组件被卸载。认为一些测试库甚至会抱怨。因此,不推荐使用此示例。【参考方案3】:

如果你按照警告的建议去做,那就最好了——在效果中调用异步函数。

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

        useEffect(() => 
            axios.get(`http://url/api/data/1`)
             .then(result => 
                setData(result.data);
             )
             .catch(console.error)
        , []);

        return (
            <div>
                this is the data["name"]
            </div>
        );
    

如果你想将 api 函数保留在组件之外,你也可以这样做:

    async function getData(userId) 
        const data = await axios.get(`http://url/api/data/$userId`)
            .then(promise => 
                return promise.data;
            )
            .catch(e => 
                console.error(e);
            )
            return data;
    


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

        useEffect(() => 
            (async () => 
                const newData = await getData(1);
                setData(newData);
            )();
        , []);

        return (
            <div>
                this is the data["name"]
            </div>
        );
    

【讨论】:

感谢您的回答。不幸的是,这正是我不想做的。如果我的 URL 发生变化,我必须在我提取的所有文件上对其进行修补。为了可扩展性,我正在尝试做一些更像我发布的第一个代码的事情。【参考方案4】:

由于 getData 返回一个 Promise,你可以使用 .then

useEffect(() => 
    getData(1).then(setData);
, []);

【讨论】:

【参考方案5】:

在解决 await 之前,组件可能会卸载或使用不同的 someId 重新渲染:

const unmountedRef = useRef(false);
useEffect(()=>()=>(unmountedRef.current = true), []);

useEffect(() => 
  const effectStale = false; // Don't forget ; on the line before self-invoking functions
  (async function() 
    // You can await here
    const response = await MyAPI.getData(someId);

    /* Component has been unmounted. Stop to avoid
       "Warning: Can't perform a React state update on an unmounted component." */
    if(unmountedRef.current) return;

    /* Component has re-rendered with different someId value
       Stop to avoid updating state with stale response */
    if(effectStale) return;

    // ... update component state
  )();
  return ()=>(effectStale = true);
, [someId]);

考虑将Suspense 用于需要在安装组件之前加载的数据。

【讨论】:

【参考方案6】:

您仍然可以在钩子之外定义异步函数并在钩子内调用它。

const fetchData = async () => 
   const data = await getData(1);
   setData(data);


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

【讨论】:

不推荐这样做,因为无法取消此异步函数中的setData cancel 是什么意思? @itwasmattgregg 你能举个取消的例子或进一步详细说明吗?除了最佳实践之外,我还没有看到任何关于为什么你不能这样做的明确答案。但是,最佳实践也可以将这些函数定义完全分解到另一个文件中。 @Dev 如果组件在 getData 正在运行时被卸载,那么 setData 会在事后尝试改变状态,react 会抛出一个警告它“表示内存泄漏”,它可能会或可能不会但是当组件不再存在时,它不应该做任何事情。这可以通过从 useEffect 返回一个设置标志的函数(react 在卸载时调用它)来避免,然后可以在调用 setData 之前检查该标志。

以上是关于如何在 React 的 UseEffect() 中调用异步函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 React useEffect 中更改路由器

如何在 useEffect 中使用 setTimeout 重置 React 钩子状态

如何在 React 中使用 useEffect 挂钩调用多个不同的 api

如何在 React 函数组件中不使用 useEffect 钩子获取数据?

如何停止 React.useEffect 的副作用? [复制]

如果它们的状态在 React useEffect 中发生变化,如何区分上下文值