好处

React16推出了流式服务端渲染,它允许你并行地分发HTML片段。这样可以让渲染

出的首字节有意义的内容给用户速度更快。



(例子1,上面部分是一次性转换,下面是流渲染,两种方式)

而且相对renderToString,流是异步的。这个可以让你的Node.js服务一次性渲染多个请求,并且保持在高请求压力环境下的及时响应。在一个持续的时间内,如果网络阻塞了,它可以停止React的渲染,并且不会因为重的网络请求影响其他轻请求。

实现: 用renderToNodeStream

import { renderToNodeStream } from 'react-dom/server';
import Frontend from '../client'; app.use('*', (request, response) => {
// 一开始发送有意义的页面部分
response.write('<html><head><title>Page</title></head><body><div id="root">');
// 把组件渲染成流,并且给Response
const stream = renderToNodeStream(<Frontend />);
stream.pipe(response, { end: 'false' });
// 当React渲染结束后,发送剩余的HTML部分给浏览器
stream.on('end', () => {
response.end('</div></body></html>');
});
});

(以上是例子2)

流是怎么工作的

renderToNodeStream返回了一个可读流,可以比作水龙头:它有源源不断的水,只是被堵住了,等待别人把它拧开。如果用这个可读流(如果把水龙头打开),只需要监听“data”时间就可以了(如果有新数据到了会自动触发)

代码类似这样:

import { renderToNodeStream } from 'react-dom/server';
const stream = renderToNodeStream(<Frontend />);
stream.on('data', (data) => {
console.log(JSON.stringify(data));
});

但是我们看例子2并没有监听”data“事件,而是直接把流pipe给了response对象。其实啊,

response对象在Node.js中是一个可写流,可以比做下水道。可读流通过pipe给可写流时,

可读流的数据会被分块发给可写流(就像是水龙头直接和下水道连接了一样,水源源不断流通)。

可写流可以对数据(水)进行任意的处理。

在我们的例子中,我们把组件渲染出来的流(html片段)给了response,response会把片段发给请求的客户端(比如浏览器)。



(可以看到First Byte明显前移动了,说明流渲染更快;因为并行的原因,片段渲染也更快了)

在Node.js中缓存HTML

const originalSend = response.send;
response.send = (html, ...other) => {
// 保证不缓存错误状态
if (response.statusCode > 100 && response.statusCode < 300) cache.put(request.path, html)
originalSend(html, ...other);
}

采用Express的中间件,提供缓存文件服务:

app.use('*', (request, response, next) => {
if (cache.has(request.path)) {
//如果缓存中有这个路径,则返回HTML
cache.get(request.path).then(html => response.send(html));
} else {
//否则就直接渲染
next();
}
})

接下来的问题是:如何渲染HTML,当你直接把htm的可写流pipe给了response流。因为response.send不会再被调用了,你也无法访问整个HTML文档。

缓存流HTML:transform streams

transform streams是什么?它就像是一个水龙头的过滤器。可读流经过它转换后才会给可写流。

在我们这里,我们只是想要缓存HTML并不会转化数据,具体怎么做,看代码:

import { Transform } from 'stream';

const createCacheStream = (key) => {
const bufferedChunks = [];
return new Transform({
//每一个HTML片段都会调用 transform()
transform(data, enc, cb) {
// 把HTML片段(Buffer)保存在内存中
bufferedChunks.push(data);
// 数据原封不动地给到下一个可读流
cb(null, data);
}, // 当所有的事件都做完后会调用flush()
flush(cb) {
// 通过连接HTML片段得到完整的html
// 并缓存起来
cache.set(key, Buffer.concat(bufferedChunks))
cb();
}
});
}

然后我们可以在我们的服务端渲染代码中使用它:

app.use('*', (request, response) => {
// 通过过滤器(transform streams)创建了缓存
let cacheStream = createCacheStream(request.path);
// cachesStream接上Response
cacheStream.pipe(response);
//先把首部分写到cacheStream
cacheStream.write('<html><head><title>Page</title></head><body><div id="root">');
// 创建了渲染流
const renderStream = renderToNodeStream(<Frontend />);
// 渲染流接上cacheStream
renderStream.pipe(cacheStream, { end: false });
renderStream.on('end', () => {
// 一旦结束渲染则做剩余部分的输出
cacheStream.end('</div></body></html>');
});
})

以上就是主要内容

原文链接:

https://zeit.co/blog/streaming-server-rendering-at-spectrum?nsukey=uW6M5M2uTd6F3Nc1u6tAVuAMGhbsBnpEuVplgUQLBlKevYZGGYN%2Bi1m6RocmDu%2F9367Zl71zSNn%2BkcWnaXcdX7bmeFLEyjbMMVsl5VtaBTfAvX8iNXgXMdbYWxVuuAealdKz8wkNfwEQtp4yLxknnredPgKISZUp%2BuR420YfPbpg8dW1lJMnpBMwNAJYMFZ0

作者知乎/公众号:前端疯 (一群热爱前端的一线程序员维护,想要用前端改变世界。)

最新文章

  1. mysql创建数据库
  2. Wcf 双工通信的应用
  3. FineUI第二天
  4. Android(java)学习笔记94:关于广播接收者的注册和使用心得
  5. AVAudioSession
  6. TCP拥塞控制 (1)
  7. 使用Json让Java和C#沟通的方法
  8. android微信付费
  9. Fast Paxos 和 Paxos的区别
  10. 洛谷 [P3973] 线性代数
  11. 如何给自己的外包APP开发报价?
  12. OS X中app store正在下载的文件在哪里
  13. Redis详解(五)------ redis的五大数据类型实现原理
  14. 缓存淘汰算法---LRU转
  15. table表格(笔记)
  16. MUST_COMPLETE
  17. rc.sysinit 解析
  18. Java之对象构造过程
  19. expect模块的使用,主要没装包折腾一晚上
  20. C#操作MSMQ(消息队列)

热门文章

  1. java实现在线支付
  2. select选中获取索引三种写法
  3. 如何使用jQuery-ContextMenu实现右击菜单
  4. Dynamics 365 Online用户密码三问及其解答
  5. HTTP的长连接,你不知道的连接。。。
  6. 【BZOJ4805】欧拉函数求和(杜教筛)
  7. [HNOI2015]亚瑟王
  8. Luogu2973:[USACO10HOL]赶小猪
  9. Luogu2045 方格取数加强版
  10. django 下拉菜单显示为object的解决办法