写点什么

React Server Components 介绍 亮点

作者:HullQin
  • 2022 年 8 月 13 日
    广东
  • 本文字数:5099 字

    阅读完需:约 17 分钟

我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。


2020 年 12 月 21 日,圣诞节前夕,React 团队发了关于 React Server Components 的博客和 RFC。一方面是公布他们关于 React 的进展,另一方面是希望吸取业界的反馈。本文解答:什么是 React Server Components?有什么亮点?

前端简史

  • 【静态】在最早的时候是根本没有前端或者后端的概念的。当时就是用 Dreamweaver 写 html 静态页面,然后部署到一台电脑的 IIS (Internet Information Services) 上。当请求这个页面时,返回这个 html 文件。

  • 【模版】再后面一点,服务端变得复杂了一些,html 页面开始使用各种模板来写,譬如 Java 系列的 FreeMarker,还有 ASP 、 PHP 等等。此时,前后端开发是一体的,最多也就是模板的编写算是最初的前端范畴,但那个时候,这个活儿往往都是现在的后端开发去干的。

  • 【Web2.0】随着 2005 年 Ajax (Asynchronous JavaScript and XML) 的诞生,彻底得改变了这一切。JS 脚本可以独立向服务器请求数据,拿到数据后,进行处理并更新网页,这个过程中,后端只负责提供数据,其他事情都由前端来做。不夸张的说,这一年算得上是 Web 开发技术发展的元年。Web 也从 1.0 的时代,步入 2.0 的时代。促进了前后端的分离。

  • 【MVC】前端可以通过 Ajax 获取数据,因此也就有了处理数据的需求,于是就促使了前端 MVC 的诞生。在这个阶段的后期,前端逐渐开始有了一点工程化的影子,并且开始受 CommonJS 的影响,有了模块化编程的概念,诞生了相应的 CMD 和 AMD 的规范。开始有了构建工具 Grunt/Gulp,开始有了编码的规范 JsLint。

  • 【MVVM】MVVM 同样是一种软件架构模式,它是在 MVC 的基础上演进过来的,去掉了 MVC 中的 Controller,增加了数据的双向绑定。最有代表性的框架就是 Google 公司推出的 Angular, 它的风格属于 HTML 语言的增强,核心概念就是数据双向绑定。

  • 【SPA】用户第一次发起页面请求时,后端返回 HTML、 CSS 和 JS 文件。JS 文件包括了页面切换逻辑的处理,这是单页应用实现的关键,它利用 Hash 或者 History 的技术,实现了当切换页面时,首先通过 Ajax 获取到新页面需要的数据,然后由 JS 根据要切换到的网址,使用获取到的数据来拼接出要展示页面的 HTML。整个切换页面的动作全部由前端来完成了。这就是单页应用,所有的资源只在第一次页面请求时被加载,后面都只会发起 Ajax 请求获取数据而已。

  • 【SSR】SPA 让 web 变成了应用的形态,它是客户端渲染(client side render)。客户端渲染有它的弊端,譬如没法做 SEO(Search Engine Optimization),由于所有的 JS 和 CSS 会在首次访问时被全部加载,并且 HTML 是在前端组装的,就势必导致首屏加载以及渲染的时间会增加,影响用户体验(不过有了代码分割,只要不需要 SEO,SPA 现在依然是最流行的方案)。目前 NextJS 是个不错的 SSR 框架。此外 umi 也支持 SSR,也支持 SSR 失败时自动降级为 CSR。

背景

Good & Cheap & Fast

Dan 在视频开头,提到了 Project Management 中的概念:一个项目中,Good、Cheap、Fast 三种属性很难同时具备,通常只能追求其二的极致。这同样适用于 React 开发:好的用户体验、低成本的代码维护、快的性能。



举个例子:一个典型的展示组件的例子。给一个作曲家 id,渲染他的详情页,里面可以看到他的 TopTracks,Discography。当然,TopTracks 和 Discography 不一定非要在详情页可以看到(其他地方也可以引用,希望它是公共的,不是详情页私有的组件)


好的用户体验、快的性能

如果能够在父组件统一获取数据,再传递数据给子组件,那么性能是快的,避免了多个 API 请求。用户体验也好。但是维护成本又高,每个数据需要一层一层传递下去,如果 API 做了改动,每个子组件都要改;此外要复用 TopTracks 和 Discography 的话,就需要在其它地方重新写获取数据的逻辑。



快的性能、低成本的代码维护

如果我们只允许用户在详情页能看到 TopTracks 和 Discography,这样这俩组件不必是公用组件,我们可以以低成本、快性能的渲染他们。但是用户必须进入详情页才能看到这些组件,用户体验不好。



低成本的代码维护、好的用户体验

我们把每个数据获取逻辑放在各个组件内,维护起来很方便,用户体验也不错。但是性能差,发送了 3 个请求,而且这些请求是「瀑布流」:先渲染 ArtistDetails,等它拿到数据后,才会继续渲染 children,children 才会开始获取数据。




这只是前端项目中一个例子,但其实很常见。(只是他们产生的原因可能不同,很多项目中产生的原因是后者请求的 API 依赖于前者的结果。上面例子只是单纯因为渲染的顺序导致了瀑布流)


已有的解决方案


已有的问题,主要是 Client 和 Server 之间来回的数据获取逻辑,一来一回,再来再回,形成了瀑布流,损耗了很多时间。GraphQL 通过一次性请求数据,拿到了所有,解决了这个问题。GraphQL + Relay (React 中用于 GraphQL 获取数据的库)这在 Facebook 内部带来了很多益处,但是难以推广普及:


  • GraphQL 并不普遍,不是所有人都会用

  • 已有的庞大项目改造为 GraphQL 成本太高(“明明是解决的前端痛点,却非要改造后端”)

  • 不是所有人都喜欢它

新的解决方案

而 React 团队提出的新的解决方案,则是这样的:



Client 发送请求,Server 直接渲染组件,并在 Server 本地获取数据,不管瀑布有多长,都可以很快的拿到所有数据,然后在 Server 渲染出组件,一次性返回给 Client。在 Server 端运行的 React Component 就是 React Server Component。在过去,前端都是 Client Component:



现在,引入 Server Component 后,组件树可能会是这样:


怎么用 React Server Component?

参考 demo:https://github.com/reactjs/server-components-demo

获取数据

NoteList.server.js


import {fetch} from 'react-fetch';
export default function NoteList({searchText}) { const notes = fetch('http://localhost:4000/notes').json();
return notes.length > 0 ? ( <ul className="notes-list"> {notes.map((note) => ( <li key={note.id}> <SidebarNote note={note} /> </li> ))} </ul> ) : ( <div className="notes-empty"> {searchText ? `Couldn't find any notes titled "${searchText}".` : 'No notes created yet!'}{' '} </div> );}
复制代码


Server Component 里可以直接获取数据(不需要像 Client Component 一样,要在 useEffect 中执行)。而且它还可以操作本地文件、甚至可以直接执行数据库查询语句。Server Component 在 Server 执行,后端能做的,它基本都可以做到。

引用其它组件

Server Component 里可以引用 Client Component。以指令的形式返回给 Client。Server Component 会将组件及其从 IO 请求到的数据序列化为特定的数据结构(称之为指令),以流的形式传递给前端:



客户端在运行时直接获取到填充了数据的流,并借助 Concurrent Mode 执行流式渲染。指令含义:


  • M:下载 Client 组件,收到后会立即下载该模块

  • J:序列化的 Server 组件,收到后该组件可以直接渲染

  • S:Symbol,suspense 是其中一种

服务端组件的依赖,不会被打包

Note.server.js 中,引用了date-fns,但是在浏览器 Sources 中看不到这个资源。而且 Server Component 全都不在里面。Amazing!

约束

Server Component 不能有状态、事件监听器。如果需要交互,只能使用 Client Component。(见 SidebarNote.js,它把“数据”(其实是 JSX)传递给了 Client Component,由 Client Component 控制不同状态的显示内容)Server Component 不能传递函数参数给 Client Component,因为函数无法被序列化。而 JSX 可序列化,所以也能传递 JSX。如果传递的 JSX 也是 Server Component,那么它会在 Server 先渲染完毕,再发送给 Client。(这意味着这部分 Server Component 也不会被 Client 下载)

混用的同时,保持 Client Component 的状态

左侧列表是个 Server Component,引用的 Client Component 保存了展开/收起的状态,如果你新增、删除一个项目,他们展开/收起的状态会被保持!Amazing!这在 CSR 时代,是需要花一定成本才能实现的功能,在 Server Component 中,re-render 直接可以保持 Client Component 的状态,非常优秀!

Shared Component 组件

有的组件以.js 结尾,而非.server.js 或.client.js,则它既可以在 Server 渲染,又可以在 Client 渲染。如果 Server Component 里用到了 Shared Component 或 Server Component,那么将会在 Server 渲染后,以指令的形式返回给 Client,他们不会被下载到 Client。如果 Client Component 里用到了 Shared Component 或 Client Component,那么会在浏览器渲染,他们会被下载到 Client。如果 Client Component 里用到了 Server Component,会发送请求。Amazing!很多系统是区分多角色的,不同角色应该看到不同的内容。这样组件级别的按需加载,正是我们想要的!


这在当前,是非常常见的 React 代码模式。但是把它切换为 Server Component 后,无编辑权限的人,不会下载 EditToolbar 组件。(CSR 模式只能做到下载但不显示)

修改+更新,只触发 1 次请求

CSR 时代,我们通常先调用 API 去修改数据,才重新 get 新的数据。但是使用 Server Component 后,我们只需要 1 次请求,就可以完成这件事!Amazing!

React IO


React 鼓励社区去开发维护这些 IO 库,可用于 Server Components。但这不意味着重新造轮子,他们只是针对已有的 node 库做了一层很薄的封装,例如 react-fs 只封装了 fs,react-fetch 只封装了 fetch。都不到一百行。封装成 React IO 库只是增加了缓存层。

关于慢请求

结合 Suspense,以流的形式渲染,开发成本低,效果好!

会取代 GraphQL 吗

不会,他们可以协作。

优势

解决瀑布流问题

0 打包体积

假设我们开发一款 MD 编辑器。服务端传递给前端 MD 格式的字符串。我们需要在前端引入将 MD 解析为 HTML 字符串的库。这个库就有 206k。import marked from 'marked'; // 35.9K (11.2K gzipped)import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)


function NoteWithMarkdown({text}) {const html = sanitizeHtml(marked(text));return (/* render */);}


通过 ServerComponent 我们怎么解决这个问题呢?只需要简单将 NoteWithMarkdown 标记为 ServerComponent,将引入并解析 MD 这部分逻辑放在服务端执行。ServerComponent 并不会增加前端项目打包体积。这个例子中,一次性为我们减少了前端 206K (63.3K gzipped)的打包体积以及解析 MD 的时间。

自动代码分割

通过使用 React.lazy 可以实现组件的动态 import。之前,这需要我们在切换组件/路由时手动执行。在 ServerComponent 中,都是自动完成的。



在上图中,左侧列表是 ServerComponent,当点击其中卡片时,组件对应数据会动态加载。

与相关技术的对比

SSR

Server Components 跟过去的 SSR 相比,你在拉取后不会丢失客户端的状态;SSR 首次访问时返回渲染完的页面,Server Components 输出的是一系列指令。Server Components 可以通过分块加载、减少打包体积等方式,进一步提升加载速度。注:二者是不同的技术方案,解决的问题不同,但可以混用。SSR 主要解决的问题是 SEO 和首屏渲染速度。

PHP 等后端框架

在 PHP/ASP 时代,页面都是由服务器来渲染。服务器接到请求后,查询数据库然后把数据“塞”到页面里面,最后把生成好的 html 发送给客户端。当用户点击链接后,继续重复上面的步骤。这样用户体验不是很好,每个操作几乎都要刷新页面,服务器处理完之后再返回新的页面。

SPA

而 Angular/Vue/React 这种单页应用(SPA)则主要是客户端渲染。服务器接到请求后,把 index.html 以及 js/css/img 等发送给浏览器,浏览器负责渲染整个页面。后续用户操作和前面的 php/jquery 一样,通过 ajax 和后端交互。但是和 php 相比,第一次访问时只返回了什么内容都没有的 idnex.html 空页面,没法做 SEO。另一点就是页面需要等到 js/css 和接口都返回之后才能显示出来,首次访问会有白屏。

推荐阅读


我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

发布于: 刚刚阅读数: 4
用户头像

HullQin

关注

公众号【线下聚会游戏】 2020.10.07 加入

game.hullqin.cn 我做了一些联机桌游网页:支持2-10人联机的UNO、2-4人联机的斗地主、2人联机的五子棋。无需下载,点开即玩!叫上朋友,即刻开局!不看广告,不做任务,享受「纯粹」的游戏!

评论

发布
暂无评论
React Server Components 介绍  亮点_CSS_HullQin_InfoQ写作社区