React 官方对 Server Comopnent 是这样介绍的: zero-bundle-size React Server Components。

这是一种实验性探索,但相信该探索是个未来 React 发展的方向,与 React Server Component 相关的周边生态正在积极的建设当中。

术语介绍

在 React Server Component (以下称 Server Component) 推出之后,我们可以简单的将 React 组件区分为以下三种:

Server Component 服务端渲染组件,拥有访问数据库、访问本地文件等能力。无法绑定事件对象,即不拥有交互性。
Client Component 客户端渲染组件,拥有交互性。
Share Component 既可以在服务端渲染,又可以在客户端渲染。具体如何渲染取决于谁引入了它。当被服务端组件引入的时候会由服务端渲染当被客户端组件引入的时候会由客户端渲染。

React 官方暂定通过「文件名后缀」来区分这三种组件:

  1. Server Component 需要以 .server.js(/ts/jsx/tsx) 为后缀

  2. Client Component 需要以 .client.js(/ts/jsx/tsx) 为后缀

  3. Share Component 以 .js(/ts/jsx/tsx) 为后缀

混合渲染

简单来说 Server Component 是在服务端渲染的组件,而 Client Component 是在客户端渲染的组件。

与类似 SSR , React 在服务端将 Server Component 渲染好后传输给客户端,客户端接收到 HTML 和 JS Bundle 后进行组件的事件绑定。不同的是:Server Component 只进行服务端渲染,不会进行浏览器端的 hyration(注水),总的来说页面由 Client Component 和 Server Component 混合渲染。

这种渲染思路有点像 Islands 架构,但又有点不太一样。

如图:橙色为 Server Component, 蓝色为 Client Component 。

React 是进行混合渲染的?

React 官方提供了一个简单的 Demo ( https://github.com/reactjs/server-components-demo ), 通过 Demo,探索一下React sever component的运作原理。

渲染入口

浏览器请求到 HTML 后,请求入口文件 - main.js, 里面包含了 React Runtime 与 Client Root,Client Root 执行创建一个 Context,用来保存客户端状态,与此同时,客户端向服务端发出 /react 请求。

// Root.client.jsx 伪代码function Root() {const [data, setData] = useState({});// 向服务端发送请求const componentResponse = useServerResponse(data);return (<DataContext.Provider value={[data, setData]}> componentResponse.render();</DataContext.Provider>);
}

看出这里没有渲染任何真实的 DOM, 真正的渲染会等 response 返回 Component 后才开始。

请求服务端组件

Client Root 代码执行后,浏览器会向服务端发送一个带有 data 数据的请求,服务端接收到请求,则进行服务端渲染。

服务端将从 Server Component Root 开始渲染,一棵混合组件树将在服务端渲染成一个巨大的 VNode。

如图,这一棵混合组件树会被渲染成这样一个对象,它带有 React 组件所有必要的信息。

module.exports = {tag: 'Server Root',props: {...},children: [{ tag: "Client Component1", props: {...}: children: [] },{ tag: "Server Component1", props: {...}: children: [{ tag: "Server Component2", props: {...}: children: [] },{ tag: "Server Component3", props: {...}: children: [] },]}]
}

不仅仅是这样一个对象, 由于 Client Comopnent 需要 Hydration, React 会将这部分必须要的信息也返回回去。

React 最终会返回一个可解析的 Json 序列 Map。

M1:{"id":"./src/BlogMenu.client.js","chunks":["client0"],"name":"xxx"}
J0:["$","main", null, ["]]
  • M: 代表 Client Comopnent 所需的 Chunk 信息

  • J: 代表 Server Compnent 渲染出的类 react element格式的字符串

React Runtime 渲染

组件数据返回给浏览器后,React Runtime 开始工作,将返回的 VNode 渲染出真正的 HTML。与此同时,发出请求,请求 Client Component 所需的 JS Bundle。

当浏览器请求到 Js Bundle 后,React 就可以进行选择性 Hydration(Selective Hydration)。

需要注意的是, React 团队传输组件数据选择了流式传输,这也意味着 React Runtime 无需等待所有数据获取完后才开始处理数据。

启动流程

  1. 浏览器加载 React Runtime, Client Root 等 js 代码

  2. 执行 Client Root 代码,向服务端发出请求

  3. 服务端接收到请求,开始渲染组件树

  4. 服务端将渲染好的组件树以字符串的信息返回给浏览器

  5. React Runtime 开始渲染组件且向服务端请求 Client Component Js Bundle 进行选择性 Hydration(注水)

Client <-> Server 如何通信?

Client Component 与 Server Component 有着天然的环境隔离,他们是如何互相通信的呢?

Server Component -> Client Component

在服务端的渲染都是从 Server Root Component 开始的,Server Component 可以简单的通过 props 向 Client Component 传递数据。

import ClientComponent from "./ClientComponent";const ServerRootComponent = () => {return <ClientComponent title="xxx" />
};

但需要注意的是:这里传递的数据必须是可序列化的,也就是说你无法通过传递 Function 等数据。

Client Component  -> Server Component

Client Component 组件通过 HTTP  向服务端组件传输信息。Server Component 通过 props 的信息接收数据,当 Server Component 拿到新的 props 时会进行重新渲染, 之后通过网络的手段发送给浏览器,通过 React Runtime 渲染在浏览器渲染出最新的 Server Component UI。

这也是 Server Component 非常明显的劣势:渲染流程极度依赖网络。

// Client Component
function ClientComponent() {const sendRequest = (props) => {const payload = JSON.stringify(props);fetch(`http://xxxx:8080/react?payload=${payload}`)}return (<button onclick = {() => sendRequest({ messgae: "something" })}>Click me, send some to server</button>)
}
// Serve Component
const ServerRootComponent = ({ messgae: "something" }) => {return <ClientComponent title="xxx" />
};

Server Component 所带来的优势

RSC 推出的背景是 React 官方想要更好的用户体验,更低的维护成本,更高的性能。通常情况下这三者不能同时获得,但 React 团队觉得「小孩子才做选择,我全都要」。

根据官方提出 RFC: React Server Components( https://github.com/josephsavona/rfcs/blob/server-components/text/0188-server-components.md ),可以通过以下几点能够看出 React 团队是如何做到"全都要"的:

更小的 Bundle 体积

通常情况下,我们在前端开发上使用很多依赖包,但实际上这些依赖包的引入会增大代码体积,增加 bundle 加载时间,降低用户首屏加载的体验。

例如在页面上渲染 MarkDown ,我们不得不引入相应的渲染库,以下面的 demo 为例,不知不觉我们引入了  240 kb 的 js 代码,而且往往这种大型第三方类库是没办法进行 tree-shaking。

// NOTE: *before* Server Componentsimport 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 */);
}

可以想象,为了某一个计算任务,我们需要将大型 js 第三方库传输到用户浏览器上,浏览器再进行解析执行它来创造计算任务的 runtime, 最后才是计算。从用户的角度来讲:「我还没见到网页内容,你就占用了我较大带宽和 CPU 资源,是何居心」。

然而这一切都是可以省去的,我们可以利用 SSR 让 React 在服务端先渲染,再将渲染后的 html 发送给用户。从这一方面看,Server Component 和 SSR 很类似,但不同的是 SSR 只能适用于首页渲染,Server Component 在用户交互的过程中也是服务端渲染,Server Component 传输的也不是 html 文本,而是 json。

Server Component 在服务端渲染好之后会将一段类 React 组件 json 数据发送给浏览器,浏览器中的 React Runtime 接收到这段 json  数据 后,将它渲染成 HTML。

我们举一个更加极端的例子:若用户无交互性组件,所有组件都可以在服务端渲染,那么所有 UI 渲染都将走「浏览器接收"类 react element 文本格式"的数据,React Runtime 渲染」的形式进行渲染。

那么除了一些 Runtime, 我们无需其他 JS Bundle。而 Runtime 的体积是不会随着项目的增大而增大的,这种常数系数级体积也可以称为 "Zero-Bundle-Size"。因此官方称之为: "Zero-Bundle-Size Components"。

更好的使用服务端能力

为了获取数据,前端通常需要请求后端接口,这是因为浏览器是没办法直接访问数据库的。但既然我们都借助服务端的能力了,那我们当然可以直接访问数据库,React 在服务器上将数据渲染进组件。

通过自由整合后端能力,我们可以解决:「网络往返过多」和「数据冗余」问题。甚至我们可以根据业务场景自由地决定数据存储位置,是存储在内存中、还是存储在文件中或者存储在数据库。

除了数据获取,还可以再开一些"脑洞"。

  • 我们可以在 Server Component 的渲染过程中将一些高性能计算任务交付给其他语言,如 C++,Rust。这不是必须的,但你可以这么做。

  • ......

简单粗暴一点的说:Nodejs 拥有什么样的能力,你的组件就能拥有什么能力。

更好的自动化 Code Split

在过去,我们可以通过 React 提供的 lazy + Suspense 进行代码分割。这种方案在某些场景(如 SSR)下无法使用,社区比较成熟的方案是使用第三方类库 @loadable 。然而无论是使用哪一种,都会有以下两个问题:

  1. Code Split 需要用户进行手动分割,自行确认分割点。

  2. 与其说是 Code Split,其实更偏向懒加载。也就是说,只有加载到了代码切割点,我们才会去即时加载所切割好的代码。这里还是存在一个加载等待的问题,削减了code split给性能所带来的好处。

React核心团队所提出 Server Component 可以帮助我们解决上面的两个问题。

  1. React Server Component 将所有 Client Component 的导入视为潜在的分割点。也就是说,你只需要正常的按分模块思维去组织你的代码。React 会自动帮你分割

import ClientComponent1 from './ClientComponent1';function ServerComponent() {return (<div><ClientComponent1 /></div>)
}
  1. 框架侧可以介入 Server Component 的渲染结果,因此上层框架可以根据当前请求的上下文来预测用户的下一个动作,从而去「预加载」对应的js代码。

避免高度抽象所带来的性能负担

React server component通过在服务器上的实时编译和渲染,将抽象层在服务器进行剥离,从而降低了抽象层在客户端运行时所带来的性能开销。

举个例子,如果一个组件为了可配置行,被多个 wrapper 包了很多层。但事实上,这些代码最终只是渲染为一个<div>。如果把这个组件改造为 server component 的话,那么我们只需要往客户端返回一个<div>字符串即可。下面例子,我们通过把这个组件改造为server component,那么,我们大大降低网络传输的资源大小和客户端运行时的性能开销:

// Note.server.js
// ...imports...function Note({id}) {const note = db.notes.get(id);return <NoteWithMarkdown note={note} />;
}// NoteWithMarkdown.server.js
// ...imports...function NoteWithMarkdown({note}) {const html = sanitizeHtml(marked(note.text));return <div ... />;
}// client sees:
<div><!-- markdown output here -->
</div>

参考自:

https://juejin.cn/post/6918602124804915208#heading-5

我们可以通过在 Server Component ,将 HOC 组件进行渲染,可能渲染到最后只是一个 <div> 我们就无需将 bundle 传输过去,也无需让浏览器消耗性能去渲染。

Sever Component 可能存在的劣势

弱网情况下的交互体验

如上文所述: React Server Component 的逻辑, 他的渲染流程依靠网络。服务端渲染完毕后将类 React 组件字符串的数据传输给浏览器,浏览器中的 Runtime React 再进行渲染。显然,在弱网环境下,数据传输会很慢,渲染也会因为网速而推迟,极大的降低了用户的体验。

Server Component 比较难能可贵的是,它跟其他技术并不是互斥的,而是可以结合到一块。例如:我们完全可以将 Server Component 的计算渲染放在边缘设备上进行计算,在一定程度上能给降低网络延迟带来的问题。

开发者的心智负担

在 React Server Component 推出之后,开发者在开发的过程中需要去思考: 「我这个组件是 Server Component 还是 Client Component」,在这一方面会给开发者增加额外的心智负担,笔者在写 Demo 时深有体会,思维上总是有点不习惯。

Nextjs 前一段时间发布了 v13,目前已实现了 Server & Client Component 。参考 Next13 的方案,默认情况下开发者开发的组件都是 Server Component ,当你判断这个组件需要交互或者调用 DOM, BOM 相关 API 时,则标记组件为 Client Component。

「默认走 Server Component,若有交互需要则走 Client Component」 通过这种原则,相信在一定程度上能减轻开发者的心智负担。

应用场景: 文档站

从上面我们可以知道 Server Component 在轻交互性的场景下能够发挥它的优势来,轻交互的场景一般我们能想到文档站。

来看一个小 Demo( https://github.com/GiveMe-A-Name/server-component-demo ), 通过这个 Demo 我们观察到几个现象:

  1. 极小的 Js bundle。

  2. 文件修改无需 Bundle。

当然像文档站等偏向静态的页面更适合 SSR, SSG,但就像前面所说的它并不与其他的技术互斥,我们可以将其进行结合,更况且他不仅仅能应用于这样的静态场景。

参考文档

  • 【react】初探server component(https://juejin.cn/post/6918602124804915208)

  • Introducing Zero-Bundle-Size React Server Components(https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html)

  • RFC: React Server Components (https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md)

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

React Server Component: 混合式渲染相关推荐

  1. react ssr php,一文吃透 React SSR 服务端渲染和同构原理

    全网最完整的 React SSR 同构技术原理解析与实践,从零开始手把手带你打造自己的同构应用开发骨架,帮助大家彻底深入理解服务端渲染及底层实现原理,学完本课程,你也可以打造自己的同构框架. 写在前面 ...

  2. React server rendering —— 网易美学主站同构实录

    此文已由作者张硕授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 网易美学主站在最初开发时,因为各种历史原因,引入了例如JQuery,Bootstrop,Angular, Re ...

  3. React SSR 服务端渲染实践指南

    年前因为工作原因需要对原有 React 项目进行服务端渲染的改造,下面是我对之前工作经验的一些总结分享,希望可以对大家有所帮助. 适用场景 首先我们来了解一下 SSR 可以做什么,可以解决什么问题,诞 ...

  4. react ssr 服务端渲染入门

    react ssr 服务端渲染入门 前言 前后端同构,作为针对单页应用 SEO 优化乏力.首屏速度瓶颈等问题而产出的解决方案,近来在 react.vue 等前端技术栈中都得到了支持.当我们正打算抛弃传 ...

  5. 前端vue适配不同的分辨率_浅析 React / Vue 跨端渲染原理与实现

    当下的前端同学对 React 与 Vue 的组件化开发想必不会陌生,RN 与 Weex 的跨界也常为我们所津津乐道.UI 框架在实现这样的跨端渲染时需要做哪些工作,其技术方案能否借鉴乃至应用到我们自己 ...

  6. react避免子组件渲染_如何与React一起使用正确的方法来避免一些常见的陷阱

    react避免子组件渲染 One thing I hear quite often is "Let's go for Redux" in our new React app. It ...

  7. [react] 和Component两者的区别是什么

    [react] 和Component两者的区别是什么 组件的state或者props更新都会触发render(),同时也会导致子组件render()重新渲染:当我们不想子组件更新时,需要手动在shou ...

  8. [react] 解释下react中component和pureComponent两者的区别是什么?

    [react] 解释下react中component和pureComponent两者的区别是什么? 组件的state或者props更新都会触发render(),同时也会导致子组件render()重新渲 ...

  9. 前端大神:如何看待 React Server Components?

    作者 | justjavac  责编 | 张文 头图 | CSDN 下载自视觉中国 来源 | justjavac(ID:justjavac-blog) 昨天看了 demo,今天翻了翻源码.我们应该从几 ...

最新文章

  1. 关于matlab向文件写入数据的方法——留着备用
  2. 谷歌、阿里们的杀手锏:三大领域,十大深度学习CTR模型演化图谱
  3. 报错解决:-bash: export: `g++11=g++': not a valid identifier
  4. sequelize的应用
  5. 国产中标麒麟Linux部署dotnet core 环境并运行项目 (三) 部署运行WEB API项目
  6. Debian7 apt源设置
  7. android adb音频采集,android adb
  8. matlab图片连续显示,Matlab利用定时器连续显示图片的问题
  9. Win 10 更新,Linux 内核的 WSL 2 开始上线
  10. 2021-2025年中国云计费行业市场供需与战略研究报告
  11. **关于小程序测试版本自己的头像与数据不显示问题**
  12. MyEclipse:详细使用教程
  13. 我的第一个python程序源代码_科学网—我的第一个Python程序——删除代码前行号的小工具 - 闫小勇的博文...
  14. 虚拟现实大作业——太阳系
  15. 微信公众号实现带图卡片分享链接
  16. swift和swiftui_在swiftui中查看布局和演示
  17. ROS1云课→19仿真turtlebot(stage)
  18. win10电脑网络适配器Wireless-AC 9560 160MHz 感叹号解决方案
  19. 【学习笔记】大数据技术之Flume
  20. NLP聊天机器人的搭建(chatbot)(一)

热门文章

  1. EXCEL填充序列快捷键
  2. idea自动填充方法参数
  3. 网页服务器 未知错误,登陆服务器发生未知错误?
  4. MSP430定时器A的计数模式的简单介绍(会中文就能看懂)
  5. JAVA类加载器包括几种?它们之间的关系是怎么样的?双亲委派机制是什么意思?有什么好处?
  6. 编译原理——数组结构的翻译
  7. iPhone4 用 iTools 的 SHSH.cfg 文件提取通用 shsh 文件(小雨伞、cydia、ifaith),降级5.0.1成功
  8. iframe页面嵌套问题
  9. skynet服务的缺陷 lua死循环
  10. json ajax 不缓存,Jsonify数据不返回ajax