React Server Component: 混合式渲染

news/2024/4/26 5:11:06/文章来源:https://blog.csdn.net/YZcoder/article/details/128100880

作者:谢奇璇

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 , 通过 Demo,探索一下React sever component的运作原理。

渲染入口

浏览器请求到 HTML 后,请求入口文件 - main.js, 里面包含了 React RuntimeClient 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,可以通过以下几点能够看出 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, 通过这个 Demo 我们观察到几个现象:

  1. 极小的 Js bundle。
  2. 文件修改无需 Bundle。

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

参考文档

  • 【react】初探server component
  • Introducing Zero-Bundle-Size React Server Components
  • RFC: React Server Components

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_38684.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud OpenFeign - - - >拦截器

源码地址&#xff1a;https://download.csdn.net/download/weixin_42950079/87209379 SpringMVC拦截器 和 OpenFeign拦截器 的区别 初学者很容易将 Spring MVC 拦截器 和 Spring Cloud OpenFeign 拦截器搞混&#xff0c;误以为OpenFeign拦截器就是SpringMVC拦截器&#xff1a; …

Kotlin高仿微信-第9篇-单聊-文本

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

Spark系列之Spark的数据倾斜

title: Spark系列 第九章 Spark的数据倾斜 9.1 Spark调优概述 ​ 有的时候&#xff0c;我们可能会遇到大数据计算中一个最棘手的问题——数据倾斜&#xff0c;此时 Spark 作业的性能会比期望差很多。数据倾斜调优&#xff0c;就是使用各种技术方案解决不同类型的数据倾斜问题…

2022腾讯全球数字生态大会【存储专场】它来了|预约有礼

它来了&#xff01;它来了&#xff01; 2022腾讯全球数字生态大会【存储专场】它来了&#xff01; 作为腾讯集团产业互联网规格最高、规模最大、覆盖面最广的年度盛会 今年存储专场与您一起探讨 分布式高性能存储与数据分析处理的科技创新和最新成果 存储会场六大亮点&…

PyQt5可视化编程-事件、信号和对话框

一、概述: 所有的应用都是事件驱动的。事件大部分都是由用户的行为产生的&#xff0c;当然也有其他的事件产生方式&#xff0c;比如网络的连接&#xff0c;窗口管理器或者定时器等。调用应用的exec_()方法时&#xff0c;应用会进入主循环&#xff0c;主循环会监听和分发事件。…

【SpringBoot】对于yaml的详细学习和三种属性赋值的实战详解

一.yaml详细讲解 1.1 什么是yaml&#xff1f; YAML是一种数据序列化语言&#xff0c;通常用于编写配置文件。业界对YAML有不同的看法。有些人会说YAML代表另一种标记语言。其他人认为“YAML不是标记语言”&#xff08;“YAML并非标记语言”&#xff09;。“YAML”只是这句话的…

CDMP选修课都有什么?

大家都知道CDMP认证考试有四个级别。分别是A级&#xff08;基础级&#xff09;P级&#xff08;实践级&#xff09;M级&#xff08;专业级&#xff09;F级&#xff08;大师级&#xff09;。级别越高&#xff0c;考试难度就越大&#xff0c;分数比例要求也更高&#xff0c;相对应…

Unity ab包加载文本 puerts 自定义loader

输出ab包 他会把你创建的ab包都打包 也就是在这里的创建的 string assetBundleDirectory Path.Combine(Application.streamingAssetsPath, "OutAssetBundles"); if (!Directory.Exists(assetBundleDirectory)) {Directory.CreateDirectory(assetBundleDirectory);…

【HIT-OSLAB-实验中的碎碎念】

文章目录应该养成的好习惯删除 替换 修改 内容时 记得留备份遇到问题要通过文字 图片 等多种途径去记录不同的项目应该在不同的文件夹进行处理代码文档 记得添加一些注释用于说明功能多输出有区别度的提示信息s找bug 先定位错误 再改当一份代码有不同版本的时候 记得说明每份代…

黎曼的几何基础,维度

黎曼的几何基础&#xff0c;让数学领先物理100年&#xff0c;维度是人类最大的障碍 - 知乎 高斯很早就有了“高维几何”的想法&#xff0c;他曾经向他的同事们说起假想完全生活在二维表面上的“书虫”&#xff0c;并想要把这推广到高维空间的几何学中去。然而&#xff0c;由于害…

进程与信号(三)

目录 一、前言 二、Signals 1、Signal Handling&#xff08;ctrlc.c&#xff09; 2、Sending Signals &#xff08;1&#xff09;alarm.c &#xff08;2&#xff09;A Robust Signals Interface &#xff08;3&#xff09;ctrlc2.c 3、Signal Sets &#xff08;1&…

ConstraintLayout布局扩展

相信大家对ConstraintLayout&#xff08;约束布局&#xff09;不陌生,这是google推出的一个强大控件&#xff0c;之所以强大其实主要归纳有两点&#xff1a;减少布局层次&#xff0c;能够轻松实现复杂布局。当然在我们实际使用过程中&#xff0c;是否真的减少了布局层次&#x…

FPGA实现图像对比度自动调整,提供2套工程源码和技术支持

目录1.算法原理介绍2.HLS算法实现3.工程1介绍&#xff1a;zynq7100实现4.工程2介绍&#xff1a;Kintex7实现5.上板调试验证6.福利&#xff1a;工程源码获取1.算法原理介绍 使用直方图均衡技术实现 使用直方图均衡技术将原始图像的灰度直方图从比较集中的某个灰度区间变成在全部…

ASCHIP_ISP Tool 工具 使用与更新

系列文章目录 ASCHIP-ISP Tool 版本1 2022 11 26ASCHIP-ISP Tool 版本1 使用说明 2022 11 26 软件介绍 对窗体进行初步配置&#xff0c;配置其大小与显示 一&#xff1a;软件介绍 ASCHIP_ISP Tool 工具是用于具有 ISP 功能型号类型的单片机进行 ISP 更新程序开发实验的配套上位…

[附源码]Python计算机毕业设计SSM隆庆祥企业服装销售管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【网络编程】第二章 网络套接字(socket+UDP协议程序)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

Java-泛型实验

1.定义一个学生类Student&#xff0c;具有年龄age和姓名name两个属性&#xff0c;并通过实现Comparable接口提供比较规则&#xff08;返回两个学生的年龄差&#xff09;&#xff0c; 定义测试类Test&#xff0c;在测试类中定义测试方法Comparable getMax(Comparable c1, Compar…

Docker-JenKins安装及配置!

Jenkins官网&#xff1a;Jenkins 安装主机配置&#xff08;官方&#xff09;&#xff1a; 最低&#xff1a; 256 MB 内存 1 GB 的驱动器空间&#xff08;尽管如果将 Jenkins 作为 Docker 容器运行&#xff0c;则建议至少 10 GB&#xff09; 小团队推荐&#xff1a; 4 GB …

英文ppt怎么翻译成中文?教你几种ppt翻译方法

ppt文件受到很多人的喜欢是因为它展示起来直观清晰&#xff0c;无论是老师在课堂上使用ppt课件来教学&#xff0c;还是在工作汇报中用ppt文件展示设计的方案或取得的成果。但当我们需要把ppt文档里的文本内容里的外语翻译成中文的时候&#xff0c;我们应该怎么做呢&#xff1f;…

003. 电话号码的字母组合——回溯算法

1.题目链接&#xff1a; 17. 电话号码的字母组合 2.解题思路&#xff1a; 2.1.题目要求&#xff1a; 给定一个仅包含数字 2-9 的字符串 digits &#xff0c;返回所有它能表示的字母组合。 数字和字母的关系&#xff1a; 例子&#xff1a; 输入&#xff1a;"23" …