《论语·前端篇》(1)- 户子

65 阅读3分钟

——从渲染到边界

子曰:
技术之学,非在名词之多,
而在边界之明、时序之辨。


一、论渲染之本

子曰:

页面者,HTML 也;
交互者,JavaScript 也。

前端之始,在于两个问题:

HTML 是何时生成的?
JS 是在何处执行的?

不明此二者,
则 CSR、SSR、RSC 之论,
皆成空谈。

二、论客户端渲染(CSR)

子曰:

CSR 者,
HTML 空而 JS 满也。

其形也

<!-- index.html -->
<body>
  <div id="root"></div>
  <script src="/app.js"></script>
</body>
// app.js
fetch("/api/data")
  .then(res => res.json())
  .then(data => {
    document.getElementById("root").innerHTML =
      `<h1>${data.title}</h1>`
  })

页面初至,无文无物;
待 JS 下载、执行、取数之后,
方始成形。

其得失

其利:

  • 架构简单
  • 前后端分离
  • 易于起步

其弊:

  • 首屏慢
  • SEO 弱
  • JS 体积膨胀

故曰:

CSR 易学,
难久。

三、论服务器渲染(SSR)

子曰:

SSR 者,
HTML 先成,而后交于浏览器也。

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/9ffd3249ba084e5fb808e33b807ec5b7~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Zu-55S75oi_5pS55oi_:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDEwNzQzMTE3Mzk1MTYzOSJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1765723134&x-orig-sign=wMYVp1fdEQv954gWto%2BxNotu8%2BM%3D

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/3a1e49ae2b984db091e757335218f798~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Zu-55S75oi_5pS55oi_:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDEwNzQzMTE3Mzk1MTYzOSJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1765723148&x-orig-sign=borGNTLsLmG1b68LCpmzfQHCMIk%3D

其形也

// server
app.get("/", async (req, res) => {
  const data = await getData()
  const html = renderToString(<Page data={data} />)
  res.send(html)
})

服务器代浏览器:

  • 获取数据
  • 渲染 HTML
  • 返回首屏内容

浏览器初见,即有其形。

其得失

其利:

  • 首屏快
  • SEO 佳
  • 用户感知好

其弊:

  • 服务器压力增大
  • 若等待所有数据,
    则慢于最慢接口

四、论 Hydration

子曰:

HTML 可见,
未必可用。

// client
hydrateRoot(
  document.getElementById("root"),
  <Page />
)

SSR 返回的 HTML:

  • 只能“看”
  • 不能“点”

JavaScript 到来后:

  • 绑定事件
  • 接管状态
  • 页面始可交互

此谓 Hydration

五、论服务器组件(RSC)

子曰:

RSC 者,
非渲染之术,
乃代码归属之道也。

https://images.ctfassets.net/e5382hct74si/kOA2be85nVBQhL0LA9zmO/71f1f53fbf206a18f791b38f8cb2fd6b/Without_React-1.png?utm_source=chatgpt.com

https://blog.saeloun.com/images/rsc/waterfall_model_client_components.jpg?utm_source=chatgpt.com

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/a4a18ca2750a4be59dafbff49b33744d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Zu-55S75oi_5pS55oi_:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDEwNzQzMTE3Mzk1MTYzOSJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1765723236&x-orig-sign=CigwRnGPhb5XT0hV8KWg3PrX%2BJA%3D

Server Component 之形

// Server Component
export default async function Page() {
  const user = await db.user.findFirst()
  return <h1>Hello {user.name}</h1>
}

其特点:

  • 只运行在服务器
  • 可直连数据库
  • 不进入浏览器 JS

六、论 RSC 非 SSR

子曰:

世人多以 RSC 为 SSR 之变,
是大误也。

SSR 关心:HTML 何时生成?
RSC 关心:代码在何处执行?

RSC 的核心价值在于:

  • 减少 JS 体积
  • 明确安全边界
  • 数据逻辑回归服务器

七、论客户端组件与服务器组件

子曰:

非曰尽为服务器,
亦非曰尽归客户端。

// Server Component
import LikeButton from "./LikeButton"

export default function Page() {
  return (
    <>
      <Article />
      <LikeButton />
    </>
  )
}
// Client Component
"use client"
export default function LikeButton() {
  return <button>Like</button>
}

Server 负责:

  • 数据
  • 结构
  • 边界

Client 负责:

  • 交互
  • 状态
  • 动效

八、论服务器边界

子曰:

浏览器者,不可信也;
服务器者,可托命也。

Client Component
  ↓
Server Action / API
  ↓
Database

客户端只发请求,
服务器才做鉴权与访问。


九、论 Token 之存与不存

子曰:

Token 不可见于 JavaScript。

Set-Cookie: auth=xxx; HttpOnly; Secure
fetch("/api/user", {
  credentials: "include"
})

客户端不知 Token,
浏览器自携 Cookie。

十、论 Server Actions

子曰:

Action 者,
服务器之事也。

"use server"
export async function createPost(formData: FormData) {
  const user = getUserFromCookie()
  await db.post.create({ userId: user.id })
}
<form action={createPost}>
  <button>Submit</button>
</form>

其形似函数,
其实为一次受控请求。


十一、论 Suspense

子曰:

Suspense 者,
声明等待之边界也。

<Suspense fallback={<Loading />}>
  <Feed />
</Suspense>

不阻全局,
先出可出之部分。

十二、论 Streaming

子曰:

Streaming 者,
非一次尽出,
乃边算边发。

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/571c96e7e9c74287a919829768de9f6b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Zu-55S75oi_5pS55oi_:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDEwNzQzMTE3Mzk1MTYzOSJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1765723380&x-orig-sign=OUV09Z8eBVe6koo7vibidTZJk4I%3D

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/f1c893afe5634ff9ac945fd531d9af8c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Zu-55S75oi_5pS55oi_:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDEwNzQzMTE3Mzk1MTYzOSJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1765723374&x-orig-sign=V1LslLJ424fOF8Rebjt%2BoHsz9tM%3D

https://p0-xtjj-private.juejin.cn/tos-cn-i-73owjymdk6/cf09f627c54d4b89b93a63daa72a3775~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Zu-55S75oi_5pS55oi_:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiNDEwNzQzMTE3Mzk1MTYzOSJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1765723376&x-orig-sign=qgH68oMLfXYUeHnTbK2fKz9oYlc%3D

HTML 先到:

<header />
<aside />
<div>Loading...</div>

内容后补:

<script>
  insertFeedHTML()
</script>

十三、论网络之象

子曰:

Streaming 之请求,
非多次,乃一次;
非骤止,乃绵延。

Network 面板中:

TTFB 很短
Content Download 很长

十六、论 RSC、SSR、Streaming 之合

子曰:

RSC 多 async,
async 必有等待;
有等待而不阻全局,
需 Suspense;
有先后而不待齐,
需 Streaming。

三者合,
则首屏速而系统稳。


十七、总论

CSR:浏览器做一切  
SSR:服务器先渲染 HTML  
RSC:服务器执行组件代码  
Suspense:声明等待边界  
Streaming:HTML 分段到达  
Server Action:安全触发服务器逻辑

十八、结语

技术之进,
非在框架之新,
而在边界之清。

知代码所处,
明职责所在,
则复杂自消。

善前端者,不乱写组件;
善架构者,不乱放代码。

善哉。