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

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

作者头像
否子戈
发布于 2024-03-25 02:26:32
发布于 2024-03-25 02:26:32
14300
代码可运行
举报
文章被收录于专栏:
运行总次数:0
代码可运行
在前端界面开发时,我们往往需要从别处(一般是后端暴露的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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
GET https://xx/api/records?id=xxx,xxx

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何避免写出高耦合低内聚的前端代码?
今天在修改其他人的一份 vue 前端代码时,在重用一个组件遇到很多问题。主要问题是这个组件很复杂,在组件里面以及组件的子组件里面,有大量不同组件会依赖 状态管理/路由参数 进行更新。 这个组件和应用整体的情况基本一样,虽然做了很多封装(大部分 重复/公用 的组件都做了封装),但总让人感觉代码非常分散,无法聚合(改一个地方可能涉及多处代码, 引用组件需要修改组件的内部逻辑等)。
zz_jesse
2024/07/04
1440
如何避免写出高耦合低内聚的前端代码?
三年经验前端vue面试记录
vue-router中两个重要组件router-link和router-view,分别起到导航作用和内容渲染作用,但是回答如何生效还真有一定难度
bb_xiaxia1998
2022/10/31
2.2K0
阿里前端二面必会react面试题总结1
在类定义中,我们可以使用到许多 React 特性,例如 state、 各种组件生命周期钩子等,但是在函数定义中,我们却无能为力,因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。
beifeng1996
2023/01/03
3K0
4-基于SpringBoot实现SSMP整合
其中核心代码是前两个注解,第一个注解@RunWith是设置 Spring 专用于测试的类运行器,简单说就是 Spring 程序执行程序有自己的一套独立的运行程序的方式,不能使用 JUnit 提供的类运行方式了,必须指定一下,但是格式是固定的,琢磨一下,每次都指定一样的东西,这个东西写起来没有技术含量啊,第二个注解@ContextConfiguration是用来设置 Spring 核心配置文件或配置类的,简单说就是加载 Spring 的环境你要告诉 Spring 具体的环境配置是在哪里写的,虽然每次加载的文件都有可能不同,但是仔细想想,如果文件名是固定的,这个貌似也是一个固定格式。似然有可能是固定格式,那就有可能每次都写一样的东西,也是一个没有技术含量的内容书写
捞月亮的小北
2023/12/01
3220
4-基于SpringBoot实现SSMP整合
从一个PR窥探React未来开发方式
useEffect依赖了a b两个状态,当其中任意一个变化后会执行fetchData请求数据。
公众号@魔术师卡颂
2021/08/26
4670
从一个PR窥探React未来开发方式
社招前端二面必会react面试题及答案_2023-05-19
利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别 和 页面元素级别
用户10358021
2023/05/19
1.6K0
欧耶!Pinia 正式成为 vuejs 的一员
Pinia 正式成为 vuejs 官方的状态库,意味着 🍍 就是 Vuex 5.x 。 先来看早期 vue 上一个关于 Vuex 5.x 的 RFC : 描述中可以看到,Vue 5.x 主要改善以下几个特性: 同时支持 composition api 和 options api 的语法; 去掉 mutations,只有 state、getters 和 actions; 不支持嵌套的模块,通过组合 store 来代替; 更完善的 Typescript 支持; 清晰、显式的代码拆分; 而 Pinia
码农小余
2022/06/16
6760
欧耶!Pinia 正式成为 vuejs 的一员
整洁架构在前端的设计思想与应用实践
行为是指系统实现的功能特性,一般是比较紧急的,需要按时上线。架构就是指系统架构,是重要的,但是并不总是特别紧急。因此导致我们常常忽视系统的架构价值,使得系统越来越难于理解、修改,导致系统功能迭代成本逐步上升,生产力逐步下降。
腾讯技术工程官方号
2023/09/06
1.2K0
整洁架构在前端的设计思想与应用实践
Redux原理分析以及使用详解(TS && JS)
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
前端小tips
2021/11/25
5K0
Redux原理分析以及使用详解(TS && JS)
Nuxt3 数据请求 useAsyncData/useFetch
Nuxt3 默认首屏SSR,由服务端渲染,可以通过配置 ssr:false 来取消服务端渲染
KID.
2023/10/30
4.3K0
我,前端,不想卷技术了……卷下整洁架构
行为是指系统实现的功能特性,一般是比较紧急的,需要按时上线。架构就是指系统架构,是重要的,但是并不总是特别紧急。因此导致我们常常忽视系统的架构价值,使得系统越来越难于理解、修改,导致系统功能迭代成本逐步上升,生产力逐步下降。
腾讯云开发者
2023/10/19
7420
我,前端,不想卷技术了……卷下整洁架构
前端接口防止重复请求实现方案
前段时间老板心血来潮,要我们前端组对整个的项目都做一下接口防止重复请求的处理(似乎是有用户通过一些快速点击薅到了一些优惠券啥的)。。。听到这个需求,第一反应就是,防止薅羊毛最保险的方案不还是在服务端加限制吗?前端加限制能够拦截的毕竟有限。可老板就是执意要前端搞一下子,行吧,搞就搞吧,you happy jiu ok。
前端老道
2024/03/25
3130
前端接口防止重复请求实现方案
小程序开发利器:深入解析数据请求插件
小程序开发中,数据请求是不可或缺的一部分。为了简化这一过程,开发者通常会使用数据请求插件。这些插件不仅封装了底层的网络请求逻辑,还提供了丰富的功能和配置选项,使数据请求变得更加高效和便捷。
小白的大数据之旅
2025/05/16
860
MPM 卖场可视化搭建系统 — 数据模型设计
这是 MPM 分享系列的第三篇。在上一篇 MPM 卖场可视化搭建系统 — 架构流程设计 中聊到数据请求的时候,我们其实没怎么细讲,那是因为在 MPM 的卖场搭建场景下,页面的数据请求经过了我们精心设计之后,足以用单独的一章来了解。面对 MPM 搭建场景下的请求繁杂、组件组合、三端同构等种种问题和诉求时,如何打造一个高效通用的数据请求解决方案,这个问题正好前阵子有机会在第三届前端早早聊大会跟大家分享和探讨,现将演讲 PPT 整理成稿,以下就是大会的分享内容。
WecTeam
2020/04/10
1.4K0
MPM 卖场可视化搭建系统 — 数据模型设计
Vue3之状态管理:Vuex和Pinia,孰强孰弱?
在前端开发中,状态管理器是一种用于管理应用程序全局状态的工具。它通常用于大型应用程序,可以帮助开发者更好地组织和管理状态,并提供一些强大的工具来简化状态的变更和使用。
用户6297767
2023/11/21
2.6K0
Vue3之状态管理:Vuex和Pinia,孰强孰弱?
vuex的五大核心_vue如何实现跨域
Vuex有5个核心概念,分别是State,Getters,mutations,Actions,Modules。
全栈程序员站长
2022/09/19
1.6K0
【Concent杂谈】精确更新策略
一晃就到2020年了,时间过得真的是飞快,伴随着q群一些热心小伙伴的反馈和我个人实际的业务落地场景,Concent已进入一个非常稳定的运行阶段了,在此开年之际,新开一个杂谈系列,会不定期更新,用于做一些总结或者回顾,内容比较随心,想到哪里写到哪里,不会抬拘于风格和形式,重在探讨和温故知新,并激发灵感,本期杂谈的主题是精确更新,文章将综合对比现有业界的各种方案,来看看Concent如何另辟蹊径,给React加上精确更新这门不可或缺的重型武器吧。
腾讯新闻前端团队
2020/01/15
1.4K0
【Concent杂谈】精确更新策略
详解整洁架构在前端的应用实践|技术创作特训营第一期
随着业务的发展,前端项目承载了越来越多的职责,前端项目也越来越复杂,简单通过cli生成的框架结构越来越无法满足需求。面对前端项目复杂度的不断提升,我们开始思考前端的架构组织方式怎么才更合理?应该如何设计良好的前端架构?行业是否有比较好的优秀实践?本文先从架构基本概念开始介绍,然后介绍整洁结构的概念和设计理念,最后结合整洁架构、 DDD方法论,一起探讨整洁架构在前端的落地应用。
欧文
2023/08/24
7420
详解整洁架构在前端的应用实践|技术创作特训营第一期
一文梭穿Vuex、Flux、Redux、Redux-saga、Dva、MobX
不管是Vue,还是 React,都需要管理状态(state),比如组件之间都有共享状态的需要。什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。
viktor
2022/09/19
5.6K0
一文梭穿Vuex、Flux、Redux、Redux-saga、Dva、MobX
滴滴前端二面必会react面试题指南_2023-02-28
为了解决跨浏览器的兼容性问题,SyntheticEvent 实例将被传递给你的事件处理函数,SyntheticEvent是 React 跨浏览器的浏览器原生事件包装器,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault()。
xiaofeng123aa
2023/02/28
2.4K0
相关推荐
如何避免写出高耦合低内聚的前端代码?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验