React Server Component: 混合式渲染

发表于 1年以前  | 总阅读数:362 次

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 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( 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 Components

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 */);
}

可以想象,为了某一个计算任务,我们需要将大型 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, 我们无需其他JSBundle。而 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>
    )
}

2 . 框架侧可以介入 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)

本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/uCwzdmuKlhpBK0REEjJ4kg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:7月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:7月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:7月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:7月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:7月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:7月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:7月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:7月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:7月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:7月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:7月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:7月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:7月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:7月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:7月以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  236860次阅读
vscode超好用的代码书签插件Bookmarks 1年以前  |  6845次阅读
 目录