从 SWR 开始 — 一窥现代请求 hooks 设计模型

简介: 本文将以 swr 为例子,讲述现在最热门的 useRequest、swr 和 react-query 三个请求 hooks 的新机制,以及新机制后 class Component 和 hooks 在设计上的区别。
作者 | 牧亿

image.png

网络请求一直是前端应用最为核心的部分之一。从 jQuery 对 ajax 的封装开始到axios,请求库这几年已经得到了快速的发展。尤其是随着 hooks 的出现,请求库终于进入了一个新的时代。

在传统的请求模型里,一个请求的完整流程是这样的:

  • 用户点击,触发请求流程
  • 设置相关视图的状态为 loading
  • 发起请求
  • 处理响应结果,关闭视图的 loading 状态

那现在的 hooks 请求库,是用了什么新的机制来优化流程,把请求这件事变得简单、用户体验更好呢?

本文将以 swr 为例子,讲述现在最热门的 useRequest、swr 和 react-query 三个请求 hooks 的新机制,以及新机制后 class Component 和 hooks 在设计上的区别。

更简单的请求处理

本部分,我们一起探索一下请求 hooks 库的新特性,感受在 swr 的 hooks 下带来的开发体验和用户体验提升。

状态更简单:Effect 状态封装

在代码层面,现在的请求库基本已经将 loading 状态封装到 hooks 中。只要你触发了请求,只需要关心 hooks 中暴露出的 data 和 error 以及 loading 状态。这一点,无论是 useRequest 和 swr 都做了通用的封装:

// ahooks 的 useRequest
const { data, error, loading } = useRequest(fetcher);

// swr
const { data, error, isValidating } = useSwr('/getList', fetcher);

缓存更简单:cacheKey 机制

针对一个场景:例如你在某购物 App 商品列表页进入一个详情页页面,然后再返回。一般情况下,App 都会保留刚刚的搜索数据。但是在 Web App 中,往往页面需要重新发起请求,获取最新的列表。如果在弱网环境下,用户面临页面空白和等待加载的场景将会非常明显。

如果我们从用户体验的角度来看,刚刚的数据也许没有过期,或者我希望返回时能看到我刚刚看到的数据,这样的体验将会更加“顺滑”。如果我们能将前页的数据缓存储存起来同时去更新数据,再由 react 的 diff 机制去更新页面,这样可以无缝的更新数据。

在 useRequest 中,可以为请求设置了一个 cacheKey 的字段,在需要重新拉取数据时,先读取缓存数据,再发起数据请求。而 SWR 和 react-query 的机制则更为激进,请求的 path 即是 key,针对这个 key 发起的请求将会被自动缓存。

const { data, error, loading } = useRequest(getList, {
  cacheKey: 'list',
});

const { data, error, isValidating } = useSwr('/getList' /** path 即是 cacheKey */, TodoService);

基于缓存的分页预加载

因为是根据请求 path 作为 key 对数据进行缓存,那么我们可以设置一个隐藏的 dom,请求当前页的下一页。当用户进入下一页的时候,已经存在下一页的缓存数据,可以达成秒开。

分页预加载 Demo(https://codesandbox.io/s/swr-cache-demo-prepage-ncywf?file=/src/App.js

更新更简单:Refetching 机制

都 2021 年了,网速对于大部分电脑来说已经不是紧张的资源了。在传统的场景下,我们只有在页面加载和用户操作主动触发时会更新数据。而对于现在的请求库来说,对于更新场景已经可以做到方便且节约了。在 SWR 中,主要实现的了聚焦重新请求、定期重新请求和重连的重新请求三种常用的场景。

对于大部分应用来说,重连重新请求是非常常用的场景。聚焦重新请求适合用在对数据即时性敏感的应用,例如商家后台。而对于即时性要求高的 App,例如协同办公型或者股票应用,可以使用 SWR 快速地实现重新请求。

编辑更简单:mutate 机制

先拉取服务端数据,进行编辑再将数据更新到服务端,这是一个很常见的场景,例如用户资料编辑的场景。传统的编辑逻辑,是在用户编辑后,实时将结果传送到服务端,再从服务端拉取编辑后的结果。如果在网络不好的场景下,会造成编辑有严重的滞后感。而在 SWR 中,编辑的思路为:通过 mutate 优先更改本地数据,然后将本地数据发送到服务端,最后从服务端拉取“验证”结果。这样,即使在网络较慢的场景下,也可以让用户很顺滑地编辑数据。

const { data, error, isValidating } = useSwr('/getList', TodoService);
return (
  <div>
    {isValidating && <span className="spin" />}
    {data}
    {error}
    <button
      onClick={() => {
        mutate('/getList', 'local edit', false);
      }}
    >
      mutate
    </button>
  </div>
)

mutate Demo(https://codesandbox.io/s/swr-mutate-demo-rcxj9?file=/src/App.js )(对 mutate 的行为,useRequest、react-query 和 swr 不一致 Demo https://codesandbox.io/s/userequest-mutate-demo-sgbo1?file=/src/App.js

当然,SWR 的 feature 还有很多,大家可以去看文档,接下来讲一下我对这些请求库的理解。

react-query、useRequest 和 swr 的区别

在对上述 feature 的处理中,基本三个请求库都实现了。但是在具体的实现细节上,三个库的定位有各自的差别。

react-query:更细

react-query几乎为每一个 feature 都提供了定制的能力,可以说是请求 hooks 里的瑞士军刀。例如,针对「浏览器窗口 focus 后重新请求这一点」,swr 和 useRequest 都只是提供开关,而 react-query 可以这样做:

 focusManager.setEventListener(handleFocus => {
   // Listen to visibillitychange and focus
   if (typeof window !== 'undefined' && window.addEventListener) {
     window.addEventListener('visibilitychange', handleFocus, false)
     window.addEventListener('focus', handleFocus, false)
   }
 
   return () => {
     // Be sure to unsubscribe if a new handler is set
     window.removeEventListener('visibilitychange', handleFocus)
     window.removeEventListener('focus', handleFocus)
   }
 })

/** 在 swr 中 */
const {} = useSWR('/getList',{
  revalidateOnFocus : false,
});

这个 hooks 的细也体现在请求的状态中。

const {
  data,error,isLoading,isError,isSuccess,isIdle,isPaused,status 
} = useMutation('name', getName);
/** 简单点~请求的状态简单点~ */

基于 react-query 的定制化能力,其很适合用来定制特定业务场景下的专属 hooks。

useRequest:更贴近 Antd

useRequest 是 umi 团队研发的 ahooks 中的一个 hooks。从设计之初,在实现分页等特性上可以脱离 antd 存在,但是也保留了可以和 antd 无缝衔接的特点。如果是使用在 antd 和 ant-design Pro 的中后场景下,我相信 useRequest 一定是效率最高的选择。

const { data, loading, pagination } = useRequest(
    ({ current, pageSize }) => getUserList({ current, pageSize }),
    {
      paginated: true,
    }
  );


<Pagination {...pagination} />
/** 不说了,懂的都懂 */

此外,useRequest 的 API 更容易上手。例如:swr 会自动发起请求,在一些需要用户被动触发发起的请求中,会有点难以控制,而 useRequest 提供了方便的 manual 属性,可以简单地控制请求的发起。如果是刚接触 hooks 请求库的新手,在使用 useRequest 之初可以当作其是一个简单的封装,很快就可以收获到在 hooks 时代下处理请求的乐趣。而且,useRquest 作为一个中文环境下的开源库,也方便大家进行讨论和探索。

swr:更顺滑……

例如对于 mutate 这个名字,有意思的是,三个库对于它的理解都是不一样的。

  • react-query:实现了 useMutation,效果类似 useRequest 里的 manual 模式。
  • useRequest:mutate 仅在本地生效。
  • swr:mutate 后,触发相关 key 全部更新。也可以暂时不发起请求,修改本地数据后再“验证”数据。同时 swr 还提供了一个全局的 mutate,你可以在任意位置发起这个请求(在不想实现 model 的轻量级场景中)。

如果说 react-query 像是安卓系统,那么 swr 就像是苹果系统:简单优雅而顺滑。swr 不认为自己在 loading 数据,而是从服务端拉取数据后,以后仅去“验证”数据是否和服务端一致。从 swr 的特性来说,我认为它更适合用来处理重前端体验的 Web App 场景。

为什么是 Hooks?

以上热门的现在请求处理库,都是基于 hooks 来封装的,为什么 class 组件不能做到呢?这里体现了 Function Component 和 Class Component 的设计理解区别。

在 Class Component 里,如果要对逻辑进行复用,可以使用 mixin 模式或者 HOC 模式。mixin 模式已经被历史抛弃了;而 HOC 模式只能复用一段逻辑,同时也不能将逻辑彻底隐藏起来(一个简单的 HOC 封装 request Demo https://codesandbox.io/s/hoc-request-demo-m002b?file=/src/App.js),而且也容易和 redux connect 以及组件的 props 混淆。

而 hooks 在设计之初,就是用来对可以复用逻辑进行封装。因此在 hooks 出现后,无论是对 redux 的封装还是今天的请求处理库,都给组件复用逻辑的能力带来了质的飞跃。

One More Thing... 现代的 hooks 模型

在经过前后端分离的浪潮后,前端行业已经得到了非常快速的发展。在基础的工具和组件得到满足之后,前端开发者的效率已经得到了很大的提升。那么前端行业的下一步在哪呢?对于大部分热爱前端行业的人来说,这个答案毫无疑问是用户体验。

虽然,在工程的角度上,大部分企业的项目都已经完成了前后端分离的研发模式;但是,在用户体验上,前端依然非常依赖和后端之间建立的网络链接。在网络环境良好的情况下,大部分 Web App 都可以提供较好的用户体验。但是随着移动化的浪潮,前端的 App 也越来越多被应用在小程序、h5 等移动环境中,而移动网络的不稳定性和 Web App 的即时渲染,都极大地影响了 Web App 的体验。

image.png

通过请求 hooks 对于请求的优化,前端应用更加独立。因为存在 cacheKey、Refetching 等机制,现在我们可以有新的角度去看待 Web App。这个时代下,Web App 更加独立,可以自身缓存一定的数据,也可以更快地响应用户的操作。通过增加静默请求和减少页面全刷新,使网络延迟和数据更新趋于无感。而这一点,hooks 和 diff 机制配合得天衣无缝。

image.png

所以,作为一个热爱前端的开发者,你还在等待什么,赶紧 hooks 起来吧!


image.png

相关文章
|
4月前
|
前端开发 Java UED
JSF 面向组件开发究竟藏着何种奥秘?带你探寻可复用 UI 组件设计的神秘之路
【8月更文挑战第31天】在现代软件开发中,高效与可维护性至关重要。JavaServer Faces(JSF)框架通过其面向组件的开发模式,提供了构建复杂用户界面的强大工具,特别适用于设计可复用的 UI 组件。通过合理设计组件的功能与外观,可以显著提高开发效率并降低维护成本。本文以一个具体的 `MessageComponent` 示例展示了如何创建可复用的 JSF 组件,并介绍了如何在 JSF 页面中使用这些组件。结合其他技术如 PrimeFaces 和 Bootstrap,可以进一步丰富组件库,提升用户体验。
56 0
|
4月前
|
开发者 Python
揭秘Django信号的神奇之处:如何解锁异步处理与插件化的秘密武器?
【8月更文挑战第31天】在现代Web开发中,Django信号作为一种强大的机制,支持异步处理与插件化扩展,使开发者能够在不改动现有代码的情况下,在关键事件点插入自定义逻辑。本文详细介绍了Django信号的概念、基本用法及其在异步任务处理与插件开发中的具体应用,展示了如何利用信号监听模型变化并触发相应操作,从而提升应用性能与灵活性,预示着信号机制在未来Web开发中的重要潜力。
28 0
|
4月前
|
前端开发 API 开发者
【前端数据革命】React与GraphQL协同工作:从理论到实践全面解析现代前端数据获取的新范式,开启高效开发之旅!
【8月更文挑战第31天】本文通过具体代码示例,介绍了如何利用 GraphQL 和 React 搭建高效的前端数据获取系统。GraphQL 作为一种新型数据查询语言,能精准获取所需数据、提供强大的类型系统、统一的 API 入口及实时数据订阅功能,有效解决了 RESTful API 在复杂前端应用中遇到的问题。通过集成 Apollo Client,React 应用能轻松实现数据查询与实时更新,大幅提升性能与用户体验。文章详细讲解了从安装配置到查询订阅的全过程,并分享了实践心得,适合各层次前端开发者学习参考。
41 0
|
7月前
|
设计模式 中间件 开发者
Koa2 的洋葱模型是什么?它是如何实现的?
Koa2 的洋葱模型是什么?它是如何实现的?
225 0
|
存储 自然语言处理 JavaScript
用有限状态机实现一个简版的html解析器
理解了状态机就如给你按上了一双翅膀,不管给你任何一段字符任容,都可以通过状态机来拆分成我们想要的结构,理解了上面这些再去看 vue 里的模板编译,你就能知道它到底是怎么加进去那些语法糖的了
112 5
|
编解码 前端开发 JavaScript
【长文慎入】一文吃透React SSR服务端同构渲染
前段时间一直在研究 react ssr技术,然后写了一个完整的 ssr开发骨架。今天写文,主要是把我的研究成果的精华内容整理落地,另外通过再次梳理希望发现更多优化的地方,也希望可以让更多的人少踩一些坑,让更多的人理解和掌握这个技术。 相信看过本文(前提是能对你的胃口,也能较好的消化吸收)你一定会对 react ssr服务端渲染技术有一个深入的理解,可以打造自己的脚手架,更可以用来改造自己的实际项目,当然这不仅限于 react ,其他框架都一样,毕竟原理都是相似的。
1434 0
|
JavaScript 前端开发 中间件
Redux原理及工作流程
Redux原理及工作流程
127 0
|
JSON JavaScript API
【从零到一手撕脚手架 | 第二节】模块化封装 降低耦合度 封装 axios pinia router
前一节我们讲解了脚手架的基础项目搭建。接下来教大家将Vue技术栈常用的工具进行封装,让我们项目的代码更易维护。
346 0
【从零到一手撕脚手架 | 第二节】模块化封装  降低耦合度 封装 axios pinia router
|
设计模式 前端开发 JavaScript
如何通俗地理解「分布式系统」;Vue是否可以在一个项目中使用多个UI框架;大厂上线流程:先上前端还是后端|极客观点
如何通俗地理解「分布式系统」;Vue是否可以在一个项目中使用多个UI框架;大厂上线流程:先上前端还是后端|极客观点
320 0
|
存储 缓存 NoSQL
【Laravel框架】对于Laravel框架架构的研究以及视图方法和内置会话在项目里的运用
【Laravel框架】对于Laravel框架架构的研究以及视图方法和内置会话在项目里的运用
295 0
【Laravel框架】对于Laravel框架架构的研究以及视图方法和内置会话在项目里的运用