ssg是后端编译时方法,也就是服务编译时会生成html,当前端请求时,服务(可能cdn)会返回编码生成的html ssr也就是后端渲染概念,现在最有名框架,nextjs rsc是19年react提出的react server component,其实也是后端渲染组件概念,具体是什么意思:
- 后端从db(可能其他)获取数据,封为rsc(JSON.str掉)数据给前端
- 前端请求到这些数据,加载业务逻辑代码
- 执行渲染流程(此时组件已经包含数据了)
- app组件子组件拿到数据并处理一句话:根据状态类型:分rcc前端运行,rsc服务运行。 这是为了处理数据链路在前端处理的繁琐步骤。 抛弃ssg,主要奖ssr和rsc区别 ssr,rsc都是服务运行时方法,也就是前端发起请求后,服务实时响应,返回结果,响应 ssr可能需要的node
区别:
ssr的常务是html,和ssg类似,浏览器可以直接解析 rsc流式输出一种类json的数据结构(bejson),由react相关插件api解析 所以两者应用场景不同
场景
SEO更需要ssr,rsc则没那么好,但是 也正是由于场景和实现都不同,所以一个应用可以同时存在ssg,ssr,rsc
RSC深究
通过.server,js..client.js来区分是哪类组件,(但是最新文档上有说明,可以在文档顶部写'use client', 'use server'区分 )
// app.server.jsx
function App() {
// 从数据库获取数据
const data = getDataFromDB();
return <Ctn data={data}/>;
}
// ctn.client.jsx
function Ctn({data}) {
// ...省略逻辑
}
组件树种可能存在交替出现的场景
Client component中不能引入RSC,比如
// ClientCpn.client.jsx
import ServerCpn from './ServerCpn.server'
export default function ClientCpn() {
return (
<div>
<ServerCpn />
</div>
)
}
这样是不允许的,因为RCC说明运行环境是浏览器前端,那子孙环境也只能是前端。而上图那样,我们是通过children来实现的。
// OuterServerCpn.server.jsx
import ClientCpn from './ClientCpn.client'
import ServerCpn from './ServerCpn.server'
export default function OuterServerCpn() {
return (
<ClientCpn>
<ServerCpn />
</ClientCpn>
)
}
这时候RSC拿到的children不是服务端代码了,而是解析完成并转化完成的rsc
协议解析
(这里的协议,其实用schema代替毕竟合适)
rsc其实是一种rpc,远程过程调用
注意,这个服务不一定是node,这个协议一般就有三个部分组成:
- 数据的序列化和反序列化
- id映射
- 传输协议举例:
// OuterServerCpn.server.jsx
import ClientCpn from './ClientCpn.client'
import ServerCpn from './ServerCpn.server'
export default function OuterServerCpn() {
return (
<ClientCpn>
<ServerCpn />
</ClientCpn>
)
}
// ClientCpn.client.jsx
export default function({children}) {
return <div>{children}</div>;
}
// ServerCpn.server.jsx
export default function() {
return <div>服务端组件</div>;
}
会被服务解析为如下:
M1:{"id":"./src/ClientCpn.client.js","chunks":["client1"],"name":""}
J0:["$","div",null,{"className":"main","children":["$","@1",null,{"children":["$","div",null,{"children":"服务端组件"}]}]}]
可以看出每行都是:
[标记][id]:json数据
其中:
- J代表组件树,M代表一个RCC应用,S代表Suspense(异常处理fallback)
- id代表数据对应id
- JSON数据代表此行具体数据##### RSC的序列化和反,其实就是json的。比如J0parse后
[
"$","div",null,{
"className":"main","children":[
"$","@1",null,{
"children":["$","div",null,{
"children":"服务端组件"}]
}
]
}
]
前端拿到json,parse后就是熟悉的类fiber格式
id映射
同一个数据,如果在两端对应上,也就是,前后端传递运行时,怎么正确找到位置对应
[
"$","div",null,{
"className":"main","children":[
"$","@1",null,{
"children":["$","div",null,{
"children":"服务端组件"}]
}
]
}
]
这个’@1‘其实就相当于正则的替换指定,这里表示的是client组件,也就是对应关系的id 1,这时候结合第一行M数据,注意M1,这个1就是id
{
"id":"./src/ClientCpn.client.js",
"chunks":["client1"],
"name":""
}
那么就开始请求这个clientjs数据,这里reactapi用的是jsonp请求,防止跨域 可以看到,通过协议中的:
- M[id],定义id对应的RCC数据
- @[id],引用id对应的RCC数据为啥M(RCC)不和J(RSC)一样直接序列化?那是因为,RCC里很多交付操作,方法,比如onClick方法,无法被JSONstri掉,传输过程必然丢失 总结下RSC协议中id映射的完整过程:
- 区分组件类型
- 后端编码时,RCC会编译出独立文件(react-server-dom-webpack插件实现,其他生态工具也有对应的插件)
- 后端如node给前端返回RSC数据J,按行展示
- react接收到J渲染对应组件树,碰到RCC,则根据对应id,查找M,发送JSONP请求
- 返回对应的RCC组件代码,pending状态由Suspense展示##### 传输协议
这是直接基于http实现,不用基于TCP,UDP
summary
本文从理念、原理角度讲解了RSC,过程中回答了几个问题。
Q:RSC和其他服务端渲染方案有什么区别?
A:RSC是服务端运行时的方案,采用流式传输。
Q:为什么需要区分RSC与RCC(通过文件后缀)?
A:因为RSC需要在后端获取数据后流式传输给前端,而RCC在后端编译时编译成独立文件,前端渲染时再以JSONP的形式请求该文件
Q:为什么RCC中不能import RSC?
A:因为他们的运行环境不同(前者在前端,后者在后端)
由于配置繁琐,并不推荐在现有React项目中使用RSC。想体验RSC的同学,可以使用Next.js并开启App Router:
在这种情况下,组件默认为RSC。 react官方提供的scr的demogithub.com/reactjs/ser…