写了十年 React,你的状态其实一直住在服务器上——HTML over WebSockets 让 SPA 回到一个代码库

你团队里那套 React SPA,代码量最大的部分从来不是业务,而是「把服务器上的状态搬进浏览器,再搬回去」——API 契约、序列化、缓存失效、乐观更新,全是这套搬运的副产品。HTML over WebSockets 换了个思路:状态就住在服务器上,浏览器只是一个「显示终端 + 事件上报口」。它是能真实落地的方案,但只在三种场景里划算,用错了会把自己锁死。

先说结论:它不是「React 替代品」,而是一个架构选型——你决定渲染层留在服务端,传输层用一条常驻双向通道。诚实地把它当选型题来答,收获很大;当成银弹来用,服务器内存会先教你做人。

它到底是什么

正常 SPA 的循环是:浏览器发请求 → 服务器返回 JSON → 客户端框架把 JSON 拼成 DOM。整条链上,你的状态在数据库和浏览器内存之间来回搬家,每搬一次就要对一次契约。

HTML over WebSockets 把这个循环拆成两半:

  • 服务器查数据、渲染模板,得到一段已经组装好的 HTML 片段;
  • 通过一条常驻 WebSocket 把片段推给浏览器;
  • 客户端那点 JavaScript 只干三件事:开通道、把片段放到该放的位置、处理动画和手势。

关键点是「浏览器端不再持有业务状态」。表格里的数据、分页游标、表单的校验规则,全都在服务端的那份会话状态里。客户端不需要知道「为什么」,只需要知道「放哪」。

这条路线不是新发明。Chris McCord 在 ElixirConf 2019 上用 Phoenix LiveView 做了个实时 Twitter 克隆,15 分钟,没写任何渲染框架的 JavaScript。此后几乎所有语言都长出了对应的实现:Phoenix LiveView(Elixir)、Laravel Livewire + Reverb(PHP)、Blazor Interactive Server(.NET/C#)、Django LiveView(Python)、Rails + Turbo(Ruby)。Django LiveView 的作者 Andros Fenollosa 把它总结成一句话:发送 HTML 而不是 JSON,停在一种语言里,把 API、契约和半个前端从待办清单上划掉。

三个变体,别选错传输层

「HTML over the wire」是一家人,区别在于 HTML 怎么走,而这直接决定延迟和双向性:

  • HTTP 请求/响应:htmx、Unicorn。最简单,无状态,能白嫖浏览器缓存和渐进增强。适合 90% 的「点一下更新一块」场景。
  • SSE 单向流:Datastar。服务器→客户端一条持续通道,覆盖消息流、通知、模型 token 流这类「只推不问」的场景,运维成本最低。
  • WebSocket 双向通道:Phoenix LiveView、Django LiveView。常驻、双向、低延迟。适合聊天、协作编辑、多人游戏、实时仪表盘。

Hacker News 上那条 87 条评论的讨论里,最高赞的「快速判据」说得很准:需要双向低延迟(聊天、协作、游戏)才上 WebSocket;只是服务器推,SSE 更简单也更便宜。 大多数应用其实用 Fetch + SSE 就够了——现代浏览器会复用同一条 TCP 连接多路复用 HTTP 请求,感知延迟和 WebSocket 差不多。

代价要提前算清

这套架构的甜头是真甜,但账单也是真的:

  1. 服务器要养连接和状态。每个在线客户端占一条常驻连接,往往还有一份服务端会话状态。水平扩容不是加机器就行,得引入共享层——Django 这边是 Channels + ASGI + Redis,Elixir 那边是分布式 PubSub。并发连接数,而不是协议本身,才是真正的瓶颈。
  2. 没有离线模式。渲染全在服务端,通道一断,页面就死在最后一帧。断线重连的 UX 和降级策略不是可选项,是必修课——弱网环境下没有它,页面会直接空白。
  3. 首屏 HTML 能被爬虫看到,后续推送不能。搜索引擎只能看到第一次服务端渲染的内容。要 SEO 的内容必须放在首屏响应里,别指望 WebSocket 补丁。
  4. HTML 比 JSON 大。这是最常被拿出来说的一条。但实测里,gzip 压缩后差距往往可以忽略,而你省下的是客户端一整个「把 JSON 变成 HTML」的框架。

什么时候真的该选它

一条可以照着走的决策路径:

  1. 先问要不要双向实时。不要 → 回到 htmx/HTTP 或 SSE,别为了炫技上 WebSocket。
  2. 要,而且你的团队只有一个后端语言栈 → LiveView 家族几乎稳赚:实时仪表盘、内部管理后台、协作工具、直播/竞拍类页面,状态就在数据旁边,广播几乎白送。
  3. 要,但项目是对外高并发 → 先算内存和共享层的账,把 sticky session(或统一通道层)在架构图里画出来再动手。
  4. 选定了,就把首屏可爬内容、断线重连 UX、并发预算这三件事写进设计文档,而不是留到上线前补。

有人用「这不过是给 MPA 加了点步骤」来嘲讽它。但换个角度看:SPA 里最容易出 bug 的那半部分——客户端状态同步——被整个删掉了,换来的是一个大家都能推导的状态图。这不是倒退,是把复杂度放回了它该在的地方。

如果你的下一个项目是内部工具、实时看板或小团队协作应用,值得用一周做个 LiveView 原型真实体感一下:你会发现省掉的不是几行代码,是整条 API 契约链。

评论区

0 条评论

登录后可评论。

阿监·前端工程化 1338 阅读