普通视图

发现新文章,点击刷新页面。
昨天 — 2025年11月23日技术

快 2026 年了,谁还在为 this 挠头?看完这篇让你彻底从懵圈到精通

2025年11月23日 16:00
前言 各位 前端er 们,谁还没被 JavaScript 里的 this 虐过?这玩意简直就是编程界的 “变脸大师”,翻脸比孙猴子还快。一会儿是全局对象,一会儿是某个实例,一会儿又跟着调用场景改头换面

浅入理解流式SSR的性能收益与工作原理

2025年11月23日 12:51

什么是流式 SSR

流式 SSR(Streaming Server-Side Rendering)是一种将服务端渲染和流式传输结合起来的技术。与传统的 SSR 不同,流式 SSR 可以在服务端渲染的同时,逐步将渲染结果传输到客户端,实现页面的渐进式展示。

在流式 SSR 中,服务端会根据客户端的请求,逐步生成页面内容,并将它们作为流式数据流式传输到客户端。客户端可以在接收到一部分数据后,就开始逐步显示页面,而不需要等待整个页面渲染完成。这种方式可以有效提高页面的加载速度和用户体验。

20251111002406.jpg (流式 SSR 的页面加载过程)

使用流式 SSR 的收益

  • 减少设备和网络情况的副作用

    这是 SSR 渲染模式相比于传统 CSR 渲染模式带来的优势,对于流式 SSR 应用同样适用。 CSR 渲染模式需要在终端设备上完成资源加载、数据加载以及整个渲染过程,受端侧设备自身 CPU 及网络性能影响大,如设备 CPU 性能不足或网络波动较大,则此时整个页面加载性能将严重下降,这也是为什么很多页面在高端设备上加载速度较快,但在低端设备上需要7-8秒的原因。

    而 SSR 的渲染模式,则在服务端提供了高性能的渲染容器及网络环境,服务端的渲染,不受端侧设备 CPU 或网络影响,始终提供稳定的渲染性能表现

    fa9a1944-b6b4-4e22-980a-47bf604c2e2c.png

  • 减少接口过慢对首屏性能的影响

    通常,页面会由多个区块组成,其中一些区块不依赖于数据,而其他区块可能依赖于快速或缓慢响应的接口。

    现有 SSR 渲染模式存在的一个不足是,整个渲染过程是同步的,需要在页面渲染之前完成所有数据请求,并一次性返回整个页面的 HTML。如果页面的某些接口响应过慢,将会导致整个页面的响应时间过长。

    流式渲染的最大好处在于它可以分块返回页面内容。例如,当请求进入时,它可以首先完成页面静态内容的渲染并响应给端侧进行渲染,等待其他依赖于数据的区块完成渲染后再分块返回。这样整个页面的渲染过程不再绑定在一起,而是一个异步的过程,先完成渲染的部分将先返回,从而优化了页面响应速度。

  • 提前资源的加载时机

    流式 SSR 相比传统 SSR 应用有另一个额外的收益是,由于 HTML 可以分块返回,页面的资源信息可以随第一个 HTML 片段一起下发,从而尽快开始加载。相比之下,在传统 SSR 应用中,资源信息必须等待整个 HTML 完成渲染后下发,请求开始的时机会受到渲染过程的阻塞。

    采用流式 SSR,可以使资源请求和页面渲染过程并行进行,进一步提升了页面的性能表现。

    752472f2-cf39-4c89-9738-e4ad3e084ba9.png

  • 提升页面的可交互时间

    在 SSR 渲染模式下,将页面节点达到可交互状态的过程称为 Hydrate,它需要在端侧执行 JavaScript。

    由于页面资源可以提前下发,并且 React 18 对 Hydrate 进行了异步化处理,在流式 SSR 应用中,可以进一步实现先渲染的页面先达到可交互状态的效果。对于部分首屏接口较慢的应用,这将进一步提升页面的可交互体验。

    20251111002828.jpg

基本工作原理

流式 SSR 的实现,最基本的原理是:

  • 基于 HTTP 协议中的 chunked 编码规范,设置响应头的 Transfer-Encoding 为 chunked 对 HTML 内容进行分块传输。
  • 在浏览器侧,流式地读取数据并进行渲染,这是主流浏览器默认支持的。

结合 Node.js 内置的 HTTP 模块,实现一个最简单的流式 DEMO 示例如下:

const http = require('http');

const server = http.createServer(async (req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.setHeader('Transfer-Encoding', 'chunked')

  // 分区块的传输页面内容
  res.write('<html>');
  res.write('<head><title>Stream Demo</title><head>');
  res.write('<body>');

  // 模拟服务端暂停
  await sleep(3000);
  res.write('<h2>Hello</h2>');

  await sleep(3000);
  res.write('<h2>ICE 3</h2>');
  res.write('</body></html>');
  res.end();
});

server.listen(3000);

基于这个基本原理,将页面分为骨架屏和几个区块,并行地渲染这些区块,然后将渲染好的区块分段返回,就可以实现基本的流式 SSR 。

WebView 接收流式Chunk渲染的实现原理(iOS)

核心组件

iOS WebView 接收流式 Chunk 渲染基于 NSURLProtocol 拦截机制 + NSURLProtocolClient 回调机制 实现。

NSURLProtocol(请求拦截器)

// 拦截 WebView 的网络请求
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    return [self shouldInterceptRequest:request];
}

- (void)startLoading {
    // 转发给自定义处理器
    [[SSRHandler shareInstance] sendRequest:self.request delegate:self];
}

NSURLProtocolClient(数据传递桥梁)

// 系统提供的协议,用于向 WebView 传递数据
@protocol NSURLProtocolClient
- (void)URLProtocol:didReceiveResponse:cacheStoragePolicy:;  // 响应头
- (void)URLProtocol:didLoadData:;                           // 数据块(可多次)
- (void)URLProtocolDidFinishLoading:;                       // 完成
- (void)URLProtocol:didFailWithError:;                      // 错误
@end

渲染流程

  • 阶段一:请求拦截

    WebView 发起请求 → NSURLProtocol 拦截 → 转发给 SSR 处理器
    
  • 阶段二:响应处理

    // 1. 返回响应头
    [client URLProtocol:protocol didReceiveResponse:response cacheStoragePolicy:policy];
    
    // 2. 流式返回数据(关键步骤)
    [client URLProtocol:protocol didLoadData:chunk1];  // 第1块
    [client URLProtocol:protocol didLoadData:chunk2];  // 第2块
    [client URLProtocol:protocol didLoadData:chunkN];  // 第N块
    
    // 3. 标记完成
    [client URLProtocolDidFinishLoading:protocol];
    
  • 阶段三:WebView 渲染

    每次 didLoadData 调用 → WebView 增量解析 HTML → 实时渲染到页面
    

数据流图示如下

┌─────────────┐    ┌──────────────┐    ┌─────────────┐
│   WebView   │───▶│NSURLProtocol │───▶│ SSR Handler │
│             │    │              │    │             │
│             │◀───│              │◀───│             │
└─────────────┘    └──────────────┘    └─────────────┘
       ▲                   │
       │                   ▼
   增量渲染          NSURLProtocolClient
       ▲                   │
       │                   ▼
   ┌─────────────────────────────┐
   │     didLoadData (chunk1)    │
   │     didLoadData (chunk2)    │
   │     didLoadData (chunkN)    │
   │  URLProtocolDidFinishLoading │
   └─────────────────────────────┘
❌
❌