前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何让前端数据请求实现奇妙的孤岛隧穿?

如何让前端数据请求实现奇妙的孤岛隧穿?

作者头像
否子戈
发布2024-03-25 10:26:32
860
发布2024-03-25 10:26:32
举报
文章被收录于专栏:
在前端界面开发时,我们往往需要从别处(一般是后端暴露的cgi接口)取数据,用于展示。在过去8年中,我经历了几家公司,几乎每次都能遇到类似的问题。当我们团队中有多个人(甚至多个团队),需要开发多个用于交付的业务组件时,数据请求的问题暴露得更加明显。

本文将详细介绍我所写的库fods的设计思路,以解决前端数据请求的破壁,让不同的人不同的团队不同的组件,可以在相同的数据请求中各自独立工作(孤岛效果)。

问题的产生

让我们先来看看我们在开发中遇到的问题,通过这些问题,我们来思考问题背后的一些本质。

最早我们在组件中,直接通过ajax请求数据,在请求到数据之后把data写到state上来处罚组件更新。但是,我们很快发现,不同的组件可能请求了相同的接口。因此,我们会尝试将ajax请求封装起来,并在不同组件中引用这个封装,在封装中,我们对data进行缓存,这样当同一个接口的数据已经存在时,请求就不会被同时发起两次。不过,这种方法还是有缺陷,因为当data数据不存在时,如果此时两个组件同时发起请求,仍然会发出同一接口的两次请求。于是,我们尝试直接缓存promise对象,这样,当第二个发起请求的组件直接获得第一个请求发起的promise,当resolved时,两个组件可以同时在then中被激发。这样看起来问题解决了。

然而,现实还是残酷的。我们发现,由于两个不同团队发布的业务组件,往往由于发布后以孤岛的形式存在于应用的不同地方。不同团队是否采用了相同的封装,需要我们在制度层面加以保障。于此同时,我们发现,特别是在一些展示数据面板的应用中,两个孤岛组件如果请求了相同的接口,却无法在因为数据发生新的推送时同步更新。这里举个例子,A和B两个组件被放在一个面板中,它们都请求了同一个接口的数据,只是采用了不同的数据子集渲染成不同的分析图表。当用户在A中输入了自己的信息,完成提交后,组件会重新拉取数据,然后重新渲染A。这很好理解。但是,重点来了,此时,难道B不应该被同步更新吗?

当我们的组件体系逐渐丰富起来,我们会开始因为数据如何传递而感到麻烦。因为我们往往将请求和state放在一个顶层组件中,再将state传递给子组件,如果组件层级比较深,就非常麻烦,因为有时候,中间组件根本不需要使用这部分数据,只是透传,太无聊了。为了应对这种跨多个组件层级的问题,我们引入全局store来解决。例如我们使用redux或vuex来定义全局状态管理器,让不同层级的组件都从store中取同一个数据来用,这样当数据发生变化时,虽处两个层级不同的组件也可同时重绘。因此,很长一段时间里,store是我们存数据的主要场所。

然而,随着我们对项目架构的需求增加,我们希望部分组件是独立的,不依赖基座应用,这些组件就像孤岛一样处于应用这片大海中,保持着自己的独立性,从而不被其他部分干扰,保证其运行的稳定性和长期维护的可靠。可此时,我们会发现,如何从store的架构中剖离出来是一个麻烦的问题。

pinia等全局状态管理器虽然解决了部分问题,让我们可以在孤岛中也可以使用全局store(或者说该store可以被多个孤岛连接),这种能够在孤岛间形成“虽互不影响但又共享数据”的局面,我称之为“孤岛隧穿”。虽然pinia解决的不错,可是我们仍然发现,pinia是挑平台的,它只能在vue的组件体系中去实践这种设计,因为它依赖了vue的reactivity的部分。

如上所述,在前端,数据请求的管理,说简单也简单,但是说麻烦也是一件非常麻烦的事,而且至今没有一种合理有效的通用方案。

问题的思考

如何让两个组件形成孤岛效应,互不影响呢?我认为核心的点,是解耦,也就是将数据请求的具体过程从组件中被解耦出去。我们在组件中,以使用者的姿态,“汲取”数据而不“生产”数据,那么对于组件而言,数据本身就像props一样,是静态的,是一个依赖项,而非一个行为项。

但是两个不同的组件,以“汲取”姿态同时依赖一个数据,并不能确保它们的关联性。就像前文说的一样,相同的数据源发生变化时,引用该数据的不同组件,应该同时全部应用新数据来重新渲染。这种既依赖,又影响,但又不直接影响的“孤岛隧穿”局面,在pinia中较好的体现出来,但在平台无关的场景下,我们不希望我们的数据被proxy包裹时,应该怎么去实现呢?

我们往往需要借助一些设计模式来实现某些能力,现在我们会引入订阅发布模式。通过订阅发布,我们可以让vue之外的任何应用都做到“孤岛隧穿”。

我们在数据源和具体应用之间,设计了一层“数据源层”。抽象出这一层的作用是将数据请求从具体的应用代码中解耦出去,做到上文提到的保持孤岛效应。数据源层暴露出的接口确保了应用层的独立性,应用层只会把数据源作为依赖,而无需关心数据源的数据是如何请求得到的,这样,我们就能让整个应用中,同一接口的数据只有一个来源。

同时,我们在数据源层实现了订阅发布,在应用层通过hooks封装,自动订阅被依赖的数据源变更,当变更发生时,组件自动更新。

如上图所示,对于A和B两位开发者而言,他们的视角范围内的东西很少,虽然在数据源层,SourceA和SourceB之间又有依赖关系,但是,在应用层,这些依赖关系是不可见的,对于B而言,他只汲取SourceB来使用,同时在用户输入的情况下,他还会调用renew(他不需要关注renew的实际过程,他可以认为renew就是刷新数据,虽然renew本质上是刷新A,但是这个过程对开发者封闭),进而整个应用中的组件都会随着renew的发生而更新界面,对于B而言,数据源层的内部逻辑,以及隐藏在更后面的数据请求,他都不需要关心,他只需要关心离自己最近的SourceB即可。

以上就是fods的全部设计思路。基于这一思路,fods可以在vue、react中使用,也可以在web、nodejs等支持javascript的应用中使用。它不依赖任何第三方库,体积小巧而稳定。

更多思考

由于在fods中,采用了订阅发布、缓存、依赖收集、hash签名等技术或思路,使用者在第一次使用时,会有些惊讶,“凭什么它可以做到这个效果?”例如下面这段代码:

代码语言:javascript
复制
const queryBook = source(async (id) => {
  const res = await fetch(`/api/book/${id}`);
  const data = await res.json();
  return data;
});

const book1 = await queryBook(1);
const book2 = await queryBook(2);
const book = await queryBook(1); // 不会发出具体请求

看似非常简单,实则这里导出的 `queryBook` 可谓大有学问。它有很强的设计哲学,例如在fods中,有一个compose接口,它用以解决批量查询中的一些问题。

例如我们有一个这样的接口:

代码语言:javascript
复制
GET https://xx/api/records?id=xxx,xxx

这个接口可以通过一次传入多个id来批量查出多个记录。然而,在我们的组件开发中,我们常常会左右为难,我到底是应该在单个组件中传1个id去查当前组件要的数据呢?还是在什么地方把所有要查的id数据一次性取出,再将单条数据传到对应的组件去呢?

使用compose则不需要担心这个问题,它会把多参数的请求进行合并,我们只需要在单个组件中关心自己的请求id,把这个id作为参数拿去请求,compose则会合并短时间内在页面中多个组件同时发起的请求,通过上面这个接口把所有需要的数据一次请求回来,于此同时,当数据回来之后,会按照其请求id的特性,把数据分配给不同的组件。

更妙的是,当我们只需要更新其中1-2个id对应的数据时,它也只拉取给定的这1-2个id对应的数据,而不会因为初始参数不同重新刷新所有数据。

代码语言:javascript
复制
const [book1, book2] = await queryBooks([1, 2]);
// 假设此处在同一时刻另外一个组件中
const [book3] = await queryBooks([3]);
// 假设完毕,优雅退出

// 上面虽然执行了两处queryBooks,但实际上,它会把所有的id合并为一个数组后,只发起一个请求

queryBooks.renew([1]);

const [newBook1] = await queryBooks([1]);

这样的巧妙设计,使得我们以前很多问题都可以迎刃而解。这完全归功于抽象出数据源层,秉持“开放封闭”原则,应用层只需要调用数据源层的对应接口即可使用,而无需关心数据源本身是如何做数据请求、如何做数据缓存、如何做数据响应的。

结语

从封装请求本身,到抽象出数据源层,我们通过将不同组件对相同数据源的诉求变为对相同事物(数据源对象)的依赖,通过这种表达上简单的关系,避免了从组件到请求到store更新再回到组件首尾循环的关系,从而提升了长期维护性。不过,任何一个工具,想要发挥它的最佳能力,还需要在实践中不断的磨合,最终找到适合项目本身的最佳姿态。

如果你对fods感兴趣,可以通过github关注,点个small~star~star会让你的心情更美丽。

https://github.com/tangshuang/fods

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 唐霜 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档