React Server Components 介绍 亮点

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开发:好的用户体验、低成本的代码维护、快的性能。
React Server Components 介绍 亮点_第1张图片
举个例子:一个典型的展示组件的例子。
给一个作曲家id,渲染他的详情页,里面可以看到他的TopTracks,Discography。当然,TopTracks和Discography不一定非要在详情页可以看到(其他地方也可以引用,希望它是公共的,不是详情页私有的组件)
React Server Components 介绍 亮点_第2张图片

好的用户体验、快的性能

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

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

如果我们只允许用户在详情页能看到TopTracks和Discography,这样这俩组件不必是公用组件,我们可以以低成本、快性能的渲染他们。但是用户必须进入详情页才能看到这些组件,用户体验不好。
React Server Components 介绍 亮点_第5张图片
React Server Components 介绍 亮点_第6张图片

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

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

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

已有的解决方案

React Server Components 介绍 亮点_第10张图片

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

  • GraphQL并不普遍,不是所有人都会用
  • 已有的庞大项目改造为GraphQL成本太高(“明明是解决的前端痛点,却非要改造后端”)
  • 不是所有人都喜欢它

新的解决方案

而React团队提出的新的解决方案,则是这样的:
React Server Components 介绍 亮点_第11张图片

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

现在,引入Server Component后,组件树可能会是这样:
React Server Components 介绍 亮点_第13张图片

怎么用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请求到的数据序列化为特定的数据结构(称之为指令),以流的形式传递给前端:
React Server Components 介绍 亮点_第14张图片

客户端在运行时直接获取到填充了数据的流,并借助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 Server Components 介绍 亮点_第15张图片

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中,都是自动完成的。
React Server Components 介绍 亮点_第16张图片

在上图中,左侧列表是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 和接口都返回之后才能显示出来,首次访问会有白屏。

推荐阅读

https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html
https://github.com/reactjs/rfcs/pull/188

你可能感兴趣的:(前端,React,react,javascript,前端,webpack,node)