使用 ahooks - useRequest 轻松实现乐观更新

904 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

这是我关于 ahooks - useRequest 系列文章的第二篇,其他两篇请查看:

在上一篇文章:使用 ahooks 中的 useRequest 轻松管理React中的网络请求 ,我们介绍了 useRequest 这个 Hook 的使用以及配置。

本文我们主要介绍如何通过 useRequest 实现乐观更新。

什么是乐观更新

乐观更新 Optimistic Updates,即不等待接口返回数据,在发起请求时就对数据进行修改,提前将交互结果显示在用户界面上。

一般的应用乐观更新的场景,是对数据源的可预测修改,即请求发出后我们可以知道成功 或者 失败 会对源数据产生什么影响。

举个栗子,假如我们要对用户信息进行修改一般涉及到如下两个接口:

  1. [get] api/user/id 获取到用户信息
  2. [put] api/user/id 修改用户信息

一般来说,接口2 在提交成功后只会返回成功或者失败的信息,那么我们通常是在接口2调用成功后,再次请求接口1更新页面。

就像我前面说的这个概念这是一个非常典型的可预测修改,我们其实完全没有必要再次请求接口1,完全可以使用我们自己已有的数据来修改原来的数据源,从而减少网络请求次数、提高用户界面响应速度。

普通:接口1 -> 接口2 -> 接口1,一发送3个请求需要等待时间较长,用户界面响应取决于网络响应

乐观更新:接口1 -> 接口2 (本地修改数据源),只发送两次请求,只等待一次接口1的响应

简单来说乐观更新的概念,就是我们乐观的认为这个修改请求会成功,并且我们可以预测成功或失败后数据源如何变化,在真实数据(服务端状态)变化之前,修改用户本地数据刷新用户界面。

如何实现

这里就要请出我们在上一篇文章介绍的 mutate 函数与生命周期回调,用我们上面修改用户信息的例子写一个简单的demo,我会分步骤来介绍每一个步骤的意义。

  1. 接口模拟

    // 模拟请求用户信息
    const fetchUserInfo = (id) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            name: `John`,
            id,
            time: new Date().toLocaleString()
          });
        }, 500);
      })
    }
    ​
    // 模拟修改用户信息接口请求成功 or 失败
    const changeUserName = (name) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (Math.random() > 0.5) {
            resolve();
          } else {
            reject(new Error('Failed to modify username'));
          }
        }, 1000);
      });
    }
    
  2. 在组件中使用 useRequest

    // 接口1 获取用户信息接口
    const { data, loading, error, run, refresh, mutate } = useRequest(() => fetchUserInfo(id));
    // 使用 usePrevious 保存上一次的数据
    const originData = usePrevious(data);
    // 接口2 修改用户接口
    const { run: updateName } = useRequest(changeUserName, {
        manual: true,
        onError: () => {
          // 乐观更新失败使用原始数据恢复
          mutate(originData);
          alert('操作失败!');
        }
      })
    

    这里我们额外引入了 ahooks 中的一个 钩子函数:usePrevious,它可以帮我们保存状态变更前的值,这样我们可以方便的通过它来回溯数据,这个钩子函数我们以后有机会会专门介绍一下。

    这里要注意,mutate 函数是来自于数据源接口,用来操作数据源状态。用于引起突变的实际是接口2,我们将它设置为手动触发,并且配置生命周期函数。当请求成功时说明对服务端状态修改成功,一般来说不需要额外操作。失败时,说明乐观更新失败,我们需要用原始数据来恢复用户界面并对用户进行提示。

  3. 处理突变

    const handleChange = () => {
        if (name) {
          // 发起修改请求
          updateName(name);
          // 发起请求时通过mutate函数乐观更新原始数据
          mutate(state => ({
            ...state,
            name
          }))
        }
      }
    

    处理突变这里很好理解,就是引起突变的请求发出后,我们就应该立即使用 mutate 函数,改变原始数据,进行乐观更新。

题外话

在 ahooks 中实现乐观更新的操作步骤上虽然有一点繁琐,但是胜在逻辑很容易理解。比较起 react-query 来说,useRequest 需要多些一些代码,例如需要自己保存修改之前的原始数据(用于突变失败后的数据恢复)。

一般来说我们在大多数情况都不是必须使用乐观更新,他不是一个必选项,只有在频繁涉及到这种可预测修改时,可以使用乐观更新来优化用户体验。

乐观更新的限定一定是可预测修改,如果一个操作之后对用户界面的影响是未知的,那么我们只能按照原来的方式处理。

个人感官上,useRequest 可以作为接触 服务端状态管理 的入门,如果我们要深入的学习理解 服务端状态管理 ,还是需要去学习 诸如 react-query 或者 rtk-query 的。

上文演示的乐观更新其实并不适合应用于生产,这更多是一个入门的demo,我会在下一篇文章介绍 swr、数据缓存、数据共享时,改造这段代码,让他更符合真实应用时的使用方法,敬请期待!