为了账号安全,请及时绑定邮箱和手机立即绑定

在 `useEffect` 中使用异步函数:最佳实践与常见陷阱

示例:Async-Await

当使用 React 构建现代 web 应用程序时,获取数据和处理副作用是一个常见的任务。useEffect 钩子可以帮助你管理这些副作用,但是在 useEffect 中使用异步代码可能会导致一些意外的问题。在这篇博客里,我们将详细探讨为什么不能直接在 useEffect 中使用 async 函数,如何正确处理异步代码以及需要避免的一些常见陷阱。我们还将探讨在 useEffect 中处理异步操作时可以采用的最佳实践和模式,以避免内存泄漏等问题。

React 中的 useEffect 是什么?

在我们深入了解 async 函数之前,让我们先快速回顾一下 useEffect 是干什么的。useEffect 是 React 中的一个钩子,允许你在函数组件中执行副作用操作。副作用可能包括但不限于以下内容:

  • 从API拉取数据
  • 订阅设置
  • 手动操作DOM
  • 管理计时器或时间间隔

钩子会在组件渲染完毕(或重新渲染)之后运行,并可以选择清理相关的副作用,当组件卸载或依赖项发生变化时。

这里是一个关于useEffect的基本例子:
    useEffect(() => {  
        document.title = "Hello World!";  
    }, []);  // 因为依赖数组为空,所以只在挂载时运行

在这个例子中,'useEffect 在组件挂载时更新页面标题。

你不知道的useEffect中的异步操作问题

为什么不能直接在 useEffect 里使用 async 函数?

React的useEffect钩子直接支持async函数,因为async函数总是返回一个Promise。但useEffect希望返回一个清理函数或什么都不返回,这种差异导致了混淆。

当你在 useEffect 中直接使用 async 函数时,它会返回一个 promise,React 不知道如何正确处理这个 promise,可能会导致意外的行为或控制台警告。

让我们看一个错误的例子,

    // useEffect 是一个钩子函数,用于处理组件的副作用
    useEffect(async () => {  
        const response = await fetch("https://api.example.com/data");  
        const data = await response.json();  
        console.log(data);  
    }, []);

这段代码看起来似乎可以运行,但它存在一些问题,因为,

  • async函数返回一个Promise,而useEffect不会返回一个Promise。
  • 如果在组件卸载时正在进行fetch操作,就无法清理掉它,这可能会导致内存泄漏的问题。
useEffect (生命周期钩子)中正确使用异步函数的方法

为解决这个问题,你应该在useEffect内部定义一个async函数并在其内部调用它来处理一个异步任务。

    useEffect(() => {  
        const fetchData = async () => {  
            try {  
                const response = await fetch("https://api.example.com/data");  
                const data = await response.json();  
                console.log(data);  
            } catch (error) {  
                console.error("获取数据时发生错误: ", error);  
            }  
        };  
    fetchData();  
    }, []); 

为什么这有效?

  • 我们在 useEffect 里面定义一个 async 函数(即 fetchData())。
  • 我们立刻运行这个函数,执行异步操作。
  • 因为我们没有从 useEffect 返回该 promise,React 在处理返回值时就不会遇到麻烦。
示例中的清理操作(处理进行中的任务)

在组件卸载前,考虑清理工作很重要。如果你在处理获取请求或其他可能需要时间的异步操作,这有助于防止未完成的异步操作导致的内存泄漏。

这里教你如何在 useEffect 中实现清理操作:

    useEffect(() => {  
        let isMounted = true; // 检查组件是否仍然挂载  
        const fetchData = async () => {  
            try {  
                const response = await fetch("https://api.example.com/data");  
                const data = await response.json();  

                if (isMounted) { // 如果组件仍挂载,则更新状态  
                    console.log(data);  
                }  
            } catch (error) {  
                if (isMounted) {  
                    console.error("获取数据出错: ", error);  
                }  
            }  
        };  
        fetchData();  
        return () => {  
            isMounted = false; // 清理函数,避免卸载后更新状态  
        };  
    }, []); // 空依赖数组:仅在挂载时执行

以下是一些关键点:

  • 我们用 isMounted 这个变量来检查组件是否还挂在那儿,在状态更新前确保没问题;
  • 当组件卸载时,清理函数会运行,确保状态更新不会发生,这样就能避免内存泄漏。
React中的异步操作(Promise和异步函数)

JavaScript 提供了 async/await 语法来处理异步操作,这种语法让异步操作更易于阅读和编写,相比传统的 promise 链。然而,在 React 的生命周期中,特别是在如 useEffect 这样的钩子中使用它们时需要格外小心。

JS中的Promise

一个 JavaScript 中的 async 函数返回一个 Promise ,它表示异步操作的完成或失败。Promise 可以处于以下状态:

  1. 等待中 — 操作进行中。
  2. 完成 — 操作成功。
  3. 被拒绝 — 操作失败了。
异步await示例:
    async function fetchData() {  
        try {  
            const response = await fetch("https://api.example.com/data");  
            const data = await response.json();  
            console.log(`获取到的数据为: ${data}`);  
        } catch (error) {  
            console.error(`错误: ${error}`);  
        }  
    }

通过使用async/await,你可以避免回调地狱(callback hell)并写出更简洁的代码。

常见陷阱
忽略长期运行的操作中的清理

如果你的 useEffect 启动了一个长时间运行的异步任务(比如从 API 获取数据),当组件卸载时,清理任何正在运行的任务非常重要。忽略这一点可能导致内存泄漏或对已卸载组件的未取消状态更新。

无需依赖任何库就能运行的异步代码

始终小心管理依赖数组 ([]),例如:如果你的 useEffect 依赖于某些 prop 或状态变量,确保将它们包含在依赖数组里,以避免意外行为的发生。

    useEffect(() => {  
        const fetchData = async () => {  
            const response = await fetch(`https://api.example.com/data/${props.id}`);  
            const data = await response.json();  
            console.log(data);  
        };  
    fetchData();  
    }, [props.id]); // 这样就能确保当props.id发生变化时,才会调用fetchData。
useEffect 中直接使用 async 函数

正如我们讨论过的,直接在useEffect里使用async被视为一个坏习惯。始终在钩子内部定义你的异步函数并调用它以确保正确的行为表现。

结论部分

useEffect 中处理异步代码可能会很棘手,但遵循正确的做法有助于避免常见的陷阱,并确保你的 React 应用程序性能良好且不会出现内存泄漏。关键要点如下:

  • 不要直接在 useEffect 中使用 async 函数。改为将它们定义在钩子内部。
  • 务必清理异步操作 以防止内存泄漏。
  • 小心管理依赖数组 以便控制效果何时运行。

通过掌握这些最佳做法,您可以在 React 应用中有信心地处理异步代码和副作用的处理。

更多阅读

如果你想更深入地了解在 React Hooks 中使用异步函数的方法,或者想了解更多最佳实践,以下是一些资源供你参考。

简单英语 🚀

感谢您加入In Plain English社区!在您离开之前,

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消