- 作者:老汪软件技巧
- 发表时间:2024-11-01 21:02
- 浏览量:
思维导图
优势分析1. #静态资源 0 Bundle Size
减少整体 JS bundle size。React 团队认为使用 Server Component 后包体积能减少 30%。
下面这张图估计每个前端项目都见识过:
React Server Component 是如何做到 Zero-Bundle-Size?只使用一次的库直接在产物中去掉。针对静态内容,RSC 直接下发计算好或者说构建好的 HTML 片段(准确来说是流),比如将 map 循环直接“展开”成多个相同 HTML 片段,有点类似 Rust 的 0 成本抽象 Zero Cost Abstraction。
示例 1:map 展开
// Note.js - Server Component
import db from 'db';
async function Note(props) {
const notes = await db.posts.list();
return (
<main>
{ notes.map(note => <h2>{note.title}h2>) }
main>
);
}
发给客户端的 HTML 片段:
<main>
<h2>Mastering React Hooks: A Comprehensive Guideh2>
<h2>Building Responsive UIs with React and CSS Gridh2>
<h2>Optimizing React Apps: Performance Tips and Best Practicesh2>
main>
示例 2:笨重的 Markdown 渲染组件
客户端组件:
// 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 */);
}
RSC:
// Server Component === zero bundle size
- import marked from 'marked'; // zero bundle size
- import sanitizeHtml from 'sanitize-html'; // zero bundle size
function NoteWithMarkdown({text}) {
// same as before
}
示例 3:利用体积庞大的 moment 时间格式化
import React from 'react'
import moment from 'moment'
export default function Header() {
return (
<header>
<div> It's {moment().format('DD/MM/YYYY hh:mm')}div>
header>
);
}
不会把笨重的 moment 打包到 JS,最终发送给客户端的只有 HTML 片段:
<header>
<div> It's 25/11/2024 17:08div>
header>
当然这个例子也揭露了 RSC 的一个弱项:只能针对静态内容做 bundle size 的优化。
实际上不是直接发送 HTML 而是通过流的形式传输数据。也就是 RSC 并不对 SEO 友好。
里面包含构建 html 的虚拟 dom 结构以及数据。
为什么不能是 HTML 片段,因为需要保持已有 DOM 元素的状态。比如输入框聚焦态、输入框正处于输入状态、或者元素正在进行 CSS 动画。
如何阅读 RSC 流协议
RSC是一种按行分隔的数据结构(方便按行流式传输),每行的格式为:
[标记][id]: JSON数据
其中:
示例:
M1:{"id":"./src/ClientCpn.client.js","chunks":["client1"],"name":""}
J0:["$","div",null,{"className":"main","children":["$","@1",null,{"children":["$","div",null,{"children":"服务端组件"}]}]}]
如上结构将解析成:
<div class="main">
<ClientCpn>
<div>服务端组件div>
ClientCpn>
div>
更多请查阅 /a/119000004…
2. #请求 减轻请求瀑布流问题
waterfall 问题并不会消失,但是通过将其从客户端挪到移动端,我们能获得更多收益。
服务端的网络情况相对客户端更可控和更稳定。
更可控:如果你的服务器运行缓慢,你可以解决这个问题,你可以让服务器变得更快,比如我们利用现代基础设施,并减少通过网络传输的数据量,但你无法控制用户的网络。
更稳定:服务端的网络情况相对而言,其 RRT 和稳定性都能得到更好的保障。
针对可以并行获取的场景,服务端并发请求要比客户端快很多。
针对无法并行获取的场景,同理通过将数据获取放到服务端,虽然串行但是仍能在一次网络请求中获得所有数据。
其次,通过 RSC 的 stream 流式传输结合 可以减少 TTFB 事件即让用户尽快看到页面而非白屏,减少白屏时间。
预请求:针对组件嵌套但是没有强依赖的情况,支持 RSC 的框架给开发者提供预请求 API,速度也能接近并行。
注意 waterfall 并没有完全解决,只是我们有了更多办法去处理它而且效果是明显要更好的。
3. #请求 零网络流量 0 Network Traffic
因为无请求,服务端直接对接 db 或者其他微服务,没有 API 网络流量消耗
4. #请求 无需暴露 API 接口 0 Public API
无需精心设计 Public API。因为数据获取直接在服务端,无需暴露 API 接口。
5. #请求 零 API 对接联调时间
无 API 无联调。
6. #请求 按需获取数据,随时裁剪
比如之前设计了一个获取多个模块数据的 API,当某个模块需要删除,我们需要让服务端接口删除对应的返回值,如果是 RSC 我们可以直接在服务端删除该组件,其内部获取数据的 sql 语句也会被一并删除。可维护性更好。
7. #静态资源 自动分包 Automatic Code Splitting
RSC 将所有客户端组件当做潜在分包点,并且可以让开发者决定下载优先级。开发者不必费心在用 lazy + 动态 import,RSC 自动帮我们做了而且能比我们做得更好,动态 import 会推迟加载时间反而抵消了分包的一些优势。
8. 代码共享
客户端和服务端组件在同一个代码库,使用同一种语言,可以共享组件、业务逻辑、utils 或者类型文件。
这是 Ruby / PHP / JSP 所不能做到的。
劣势分析1. 交互组件无法适用
因为运行在服务端,state 以及 lifecycle 相关的 hooks、浏览器 API、事件处理等都不能使用。
2. 对 SEO 不友好 ️
因为 RSC 输出的是流式的协议数据,而非 HTML 片段,如果需要 SEO 友好则要结合 SSR。
FAQRSC 是编译时还是运行时?
答:编译+运行。第一层编译(webpack)会将只会使用一次的笨重的依赖移除(本文中的 moment、marked 等),最后输出编译后的数据流是每次请求都会重新从服务端获取(这就是所谓的运行时)。SSG(Static Site Generation)才是真正的编译时。
比如,每次运行都会执行判断是否今天,故 dayjs 无法在编译时去除。
import dayjs from 'dayjs'
export default function Header() {
const time = dayjs(note.time).isToday() ? dayjs(note.time).format('YYYY-MM-DD HH:mm:ss') : dayjs(note.time).format('HH:mm:ss')
return (
<header>
<div>{ time }div>
header>
);
}
RSC 和 SSR 是一回事吗?
答:最大区别 SSR 输出的是 HTML,RSC 输出的是数据流,客户端负责拼装为 HTML。二者可以一起使用。
参考