在 useEffect 中使用异步函数指南

96 篇文章 6 订阅
22 篇文章 1 订阅

在 useEffect 中使用异步函数指南

useEffect 通常是 React 中进行数据获取的地方。数据获取意味着使用异步函数,在 useEffect 中使用它们可能不像你想象的那么简单。看完本文,你将会真正掌握在useEffect 中使用异步函数的正确姿态。

1. 错误方式

useEffect 中有一个错误的数据获取方法。如果你编写以下代码,你的 Linter 将向你发错警告:

// ❌ 不能这么做
useEffect(async () => {
  const data = await fetchData();
}, [fetchData])

这里的问题是 useEffect 的第一个参数应该是一个函数,它要么不返回任何东西(未定义),要么返回一个函数(以清除副作用)。但是 async 函数返回 Promise,它不能作为函数调用。这不是 useEffect hook 所期望的第一个参数。

那么如何在 useEffect 中使用异步代码呢?

2. 在 useEffect 中写入异步函数

通常解决方案是简单地在 useEffect 内部编写数据获取代码,像这样:

useEffect(() => {
  // 声明数据获取函数
  const fetchData = async () => {
    const data = await fetch('https://yourapi.com');
  }

  // 调用函数
  fetchData()
    // 确保捕获任何错误
    .catch(console.error);
}, [])

需要注意的一点是,如果希望使用来自异步代码的结果,那么应该在 fetchData 函数内部而不是外部执行。例如,以下情况会导致问题:

useEffect(() => {
  // 声明异步数据获取函数
  const fetchData = async () => {
    // 从 API 获取数据
    const data = await fetch('https://yourapi.com');
    // 将数据转换为 json
    const json = await data.json();
    return json;
  }

  // 调用函数
  const result = fetchData()
    // 确保捕获任何错误
    .catch(console.error);

  // ❌ 不要这样做,它不会像你期望的那样运行
  setData(result);
}, [])

当调用 setData(result) 行时,你能猜出 result 变量是什么吗?

理解这一点的一种方法是编写一个只等待一定时间的异步函数。

useEffect(() => {
  // 声明异步数据获取函数
  const fetchData = async () => {
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    //等待 1000ms
    await sleep(1000);
    return 'Hello World';
  };

  const result = fetchData()
    // 确保捕获任何错误
    .catch(console.error);

  // 输出结果是什么?	
  console.log(result);
}, [])

输出结果是什么?

如果你答对了,恭喜你!result 将保存一个 pending 状态的 Promise 对象。在控制台中,你会看到如下内容:

Promise {<pending>}

那么如何在 useEffect 中使用异步代码的结果呢?答案是在获取数据函数内部。要修改上面的例子,你可以这样做:

useEffect(() => {
  // 声明异步数据获取函数
  const fetchData = async () => {
    // 从 API 获取数据
    const data = await fetch('https://yourapi.com');
    // 将数据转换为 json
    const json = await response.json();

    // 使用 result 设置状态 
    setData(json);
  }

  // 调用函数
  fetchData()
    // 确保捕获任何错误
    .catch(console.error);;
}, [])

3. 如果需要从 useEffect 外部提取函数该怎么办?

在某些情况下,你希望在 useEffect 之外有数据获取函数。在这些情况下,你只需要小心地用 useCallback 包装函数。

为什么?因为函数是在 useEffect 之外声明的,所以必须把它放在 hook 的依赖数组中。但是如果函数没有被包装在 useCallback 中,它将在每次重新渲染时更新,从而在每次重新渲染时触发 useEffect。这不是我们想要的结果。

// 声明异步数据获取函数
const fetchData = useCallback(async () => {
  const data = await fetch('https://yourapi.com');

  setData(data);
}, [])

// useEffect 只在适当的时候调用 fetchData
useEffect(() => {
  fetchData()
    // 确保捕获任何错误
    .catch(console.error);;
}, [fetchData])

在前面展示了一个在 useEffect 中获取数据的示例,有一个点需要注意:

useEffect(() => {
  // 声明异步数据获取函数
  const fetchData = async () => {
    // 从 API 获取数据
    const data = await fetch('https://yourapi.com');
    // 将数据转换为 json
    const json = await response.json();

    // 使用 result 设置状态 
    setData(json);
  }

  // 调用函数
  fetchData()
    // 确保捕获任何错误
    .catch(console.error);;
}, [])

你通常需要一种能够取消 setData 调用的方法。在上面的例子中,它是没有用的,因为调用只执行了一次,但我们假设调用依赖于一个参数 param

useEffect(() => {
  // 声明异步数据获取函数
  const fetchData = async () => {
    // 从 API 获取数据
    const data = await fetch(`https://yourapi.com?param=${param}`);
    // 将数据转换为 json
    const json = await response.json();

    // 使用 result 设置状态 
    setData(json);
  }

  // 调用函数
  fetchData()
    // 确保捕获任何错误
    .catch(console.error);;
}, [param])

如果 param 改变了值,fetchData 将被调用两次。如果这发生得很快,就有可能出现一个竞争条件,即第一个调用在第二个调用之后解决,因此状态将保持旧的值。

解决这个问题的方法是使用一个变量来控制是否更新状态。

useEffect(() => {
  let isSubscribed = true;

  // 声明异步数据获取函数
  const fetchData = async () => {
    // 从 API 获取数据
    const data = await fetch(`https://yourapi.com?param=${param}`);
    // 将数据转换为 json
    const json = await response.json();

    // 如果 'issubscriptions' 为 true,则用结果设置 state
    if (isSubscribed) {
      setData(json);
    }
  }

  // 调用函数
  fetchData()
    // 确保捕获任何错误
    .catch(console.error);;

  // 取消 setData
  return () => isSubscribed = false;
}, [param])

这是在可能多次触发 useEffect 中获取数据时非常常见的模式。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏安   

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值