在2020年底,React宣布了一项全新的功能:服务器组件。当时还处于研究和试验阶段,还没有正式发布。随着React18.0的正式发布,ServerComponents的脚步越来越近了嗯,不出意外的话,应该会在今年的React18小版本中正式发布。ServerComponents听上去并没有那么令人兴奋,React18发布的各种特性也显得平淡无奇。Hooks出来三年多了,React似乎停滞不前了。它只是建立在现有的基础上做一些小修小补?不是,并发渲染(React18带来的新特性)是一个本质的改变。它不像Hooks那样在开发体验上有近乎翻天覆地的变化,但是这种底层渲染能力/机制的调整会带来非常非常多的可能性,比如:Suspense、OffScreen、ServerComponents目前还没有可用于生产,但当它们在未来正式发布并逐渐大规模使用时,每一个特性都会带来非常显着的开发体验提升。而如果让我选出最受期待的未来出现的新特性之一,那无疑是ServerComponent。那么,服务器组件到底是什么?他会不会像当年的Hooks那样对整个React生态产生巨大的影响?在我们回答这些问题之前,有必要解释一下服务器组件是什么以及它们解决什么问题。注:以下很多内容的灵感来自Dan和Lauren的这个讲座视频[1]。如果你想了解更多关于即将推出的React服务器组件,强烈推荐这个视频。其实这篇文章并不是ServerComponents的使用教学,不会涵盖ServerComponents的每一个细节(甚至为了表述方便,有意略过一些细节),所以在阅读下文之前,最好对Server有一些了解组件背景:前后端分离“前后端分离”是目前主流的web研发模式。后端存储数据,并将对数据的操作(增删改查)封装成接口,通过后端服务提供给前端,前端应用发送请求(如http请求或rpc请求)调用后端提供的接口获取数据或修改数据。这可能是十多年来非常普遍的研发模式。所以,我们分为前端开发和后端开发,各自负责“楚江汉世界”的一方。我们在每一方面都做了很多优化、创新和突破。在后端,我们有容器化、微服务和SSR。在前端,我们有代码拆分、前端路由和ReactHooks。但是对于API层,这么多年我们好像从来没有关注过。即便做了,也只是停留在API的传输性能(比如grpc),API的存在形式(比如Restful和GraphQL),API的工程化管理(比如Postman)。我不想说API是邪恶和糟糕的设计,但是从Restful的概念提出到现在已经过去了22年。我们现在是否应该重新思考:以网络请求作为前后端边界是否是最好的方案?我们如何在没有API的情况下构建和开发Web应用程序?症结让我们回到刚才的画面,思考一下API除了职责分工明确之外,还带来了哪些问题。请求瀑布(Waterfall)如Remix[2]主页所示,基于API和嵌套路由的前端站点在请求时会出现瀑布现象:数据之间可能存在前后端依赖,或者是强耦合与组件,需要等待组件的bundle加载完成后才能发送请求,从而导致请求瀑布现象。并发请求后端希望实现一个小巧美观的接口,每个接口都有独立的职责,例如:getUser获取用户信息getSongs?page=12获取歌曲列表getNotifactions获取通知列表getFavoirateSongs获取喜欢的歌曲getNewSongs获取最新发布的歌曲getRecommendSong获取今日歌曲推荐歌曲及对应文案getSearchBarHotKeywords获取热门搜索词getAdBanner获取广告banner内容getRecentSongs获取最近收听记录getRecommendedPlayList获取推荐播放列表列表...(太多了)每个界面可以单独查看比较合理,但是放在一起,你会发现用户每次打开这样的音乐网络应用程序时,至少会发送十几个界面。对于一些稍微复杂一点的网页,第一次加载不需要请求几十个接口。陌生感。每个接口请求都会带来网络开销,甚至在某些环境下会有最大并发请求数的限制(比如支付宝客户端的rpc请求)。或许网络层的自动批处理可以解决这个问题,但不幸的是,在目前的技术体系中,这个问题并不容易解决(这里不写不能解决,因为确实有一些可行的方案,比如BFF和依赖网关做接口聚合,但是都会引入新的问题)。前端包大小(Bundlesize)Bundlesize在“现代”前端开发领域一直饱受诟病。数百千字节的js文件,似乎已经偏离了浏览器“浏览”网页的初衷。并不是说我们都必须是浏览器原教旨主义者,但是如果能够在不影响用户体验和开发体验的情况下将网页恢复到非常轻量级和快速的状态,那岂不是一件好事吗?在我个人看来,协作成本(通信、逻辑意识和关闭)是需要长期维护的大型项目或应用程序中最头疼的问题。假设我们现在有一个非常庞大的应用程序,需要十几个开发人员来编写和维护。怎么分工?答案一定是先做模块化。我们将整个应用程序拆分成几个尽可能相互独立的模块,然后每个人或几个人负责其中一个模块。模块化的好处是边界清晰(看到一个需求就可以确定哪些变化涉及哪些或哪些模块),职责明确(每个人都有自己的职责),减少沟通成本(由于逻辑封闭,不需要外部感知,因此可以降低通信成本)。对于前两点,目前的前后端分离架构还是合格的,但是对于第三点,我认为基于网络请求接口的协作模型并没有有效实现逻辑内部封闭,减少对在许多情况下是前端和后端。之间来回传递的信息量。比如这样一个页面:看起来很简单,显示一些信息,加上一个充值按钮,这就是我最初的设想。但是,随着项目的不断推进,我发现,我以为是纯静态的标题文案,其实需要后台控制,文案内容是根据当前用户所在的组动态判断的;后台预处理后需要在前端显示可靠性问题、折扣和实际支付相关内容;我发现需要后台返回倒计时的参考时间;我发现按钮的文案和点击行为,需要后台控制,尤其是按钮的点击行为。最后的解决方案是后端返回一个枚举,前端使用这个值来切换大小写,走不同的逻辑(比如下单、先导、注册、绑定卡)……为了阅读体验,我只是列举了其中的一小部分随手想到的。如果我总结一下,就是因为“前后端分离”,导致后端和前端没有解耦。还是很乱。后端感知了太多的前端视图层逻辑,就像发明了一套DSL(DomainSpecificLanguage),而前端需要为这套DSL写解析器和渲染器。回到我们刚才说的,模块化的好处。模块化能够降低通信成本,还有一个不可忽视的前提,那就是架构的合理性。模块化并不是降低通信成本的本质原因,也不是所有的模块化实践都能降低通信成本。目前后端分离的做法已经成为一种僵化、僵化的“常态”,那么它真正能降低多少沟通成本呢?一个大大的问号。ServerComponents重申,以下假设读者朋友已经了解ServerComponents[3]基于网络请求的API模型有一个很大的前提,即前端应用和后端应用是两个独立的应用,但是为什么一定要会这样吗?或许我们可以让后端应用直接渲染HTML,在用户操作时重新渲染页面?这其实就是Restful时代之前的架构,缺点很多,尤其是交互性差,不然后面Restful也不会流行起来。那么也许,我们可以让前端的React组件在后端运行?这是React服务器组件。一张图片胜过千言万语。在目前的前后端分离模式下,后端提供接口,前端React组件调用接口。而如果后端能够运行React组件,直接将React节点树渲染到前端,就不需要所谓的API概念了。在后端运行React组件并不是什么新鲜事。在SSR(ServerSideRendering)中我们早就习惯了,但是需要注意的一点是,在SSR中,后端运行React组件,会生成一个初始状态的html,但是这个html不是交互式的,它只是一种改进的、修补的优化,允许用户尽快看到该页面。ServerComponents带来的是,我们可以在同一个项目中将一部分组件作为ServerComponents使用,另一部分作为ClientComponents使用,这样既可以享受到后端内部调用带来的便利性和可维护性,又可以保证交互性几乎没有妥协的页面。如果你用过PHP或者Django,那么你一定对这种模式非常熟悉:后端直接渲染html内容,浏览器只负责显示。当用户点击按钮时,页面将被重新请求并重新渲染。如果页面上需要一些复杂的内容动态交互,比如允许用户展开/收起一个列表,或者点击按钮后显示模态框,都可以借助jQuery来实现。PHP+bootstrap+jQuery,现在,ServerComponents就像是这种范式的升级版,堪称全新的“全栈”开发模式。因为是在后端环境,这些ServerComponents可以使用后端所有的能力,无论是中间件,还是其他后端微服务调用,甚至是dbaccess(当然也可以直接跑sql,但是更好的做法是通过数据中间层),可以实现。这样一来,我们就可以直接从源头获取数据,放到React组件的上下文中,自然不需要传统意义上的API。更准确的说,API并没有消失,我们不会和API说再见,而是让它改变形态。哪里有模块化,哪里就有API。Restfulhttp网络请求是API,但是中间件暴露的方法,浏览器提供的Date对象,node提供的文件读取功能,db提供的SQL都是API。在这种新的架构下,API成为了后端业务应用和上游服务之间的调用,以及ServerComponents和ClientComponents之间的props传递。前者让API更简洁,更符合单一职责原则,后者让API自然到你几乎察觉不到。所以:ServerComponents让我们不再按照前端-后端来拆分模块,而是按照业务应用-底层服务来进行更合理的模块拆分。这在理论上可以减少模块之间的通信成本(因为没有办法在实践中证明)。由于ServerComponents是在后端运行组件,通过网络直接传输到前端渲染,所以很多大容量的包(如markdown渲染,htmlsanitize)不需要在前端下载运行,大大减少包装尺寸。由于对底层db或upstream服务的调用都发生在后端内部,即使有并发请求,所产生的开销也远小于前端并发调用后端RestfulAPI的开销。同理,请求瀑布流的问题也会因为调用开销的减少而消失或减轻。想象一下,如果你大胆想象,未来的研发模式可能是这样的:开发者将不再区分前端和后端,而是区分业务应用开发和上游服务开发。现在的后端开发将(真的)不再需要关注视图逻辑,而只关注底层的业务逻辑,为前端提供清晰、易用、原子化的服务/接口;而现在的前端开发会扩展到前端和后端(在代码运行环境上),负责基于后端封装的原子底层能力构建视图层,我们也需要一个新的集合适应服务器组件的框架和基础设施。目前ServerComponents还没有正式发布,即使正式发布之后,距离工程化落地还有很长的路要走。ServerComponents增加了很多额外的限制,server、client、shared的区分也可能带来一些理解成本。缓存、性能、服务器重新渲染时的增量更新策略、发布时的灰度和回滚、业务中边界条件的处理,还有很多问题需要解决,还有很多未知没有解决。核实。
