闽公网安备 35020302035485号
const Koa = require('koa');
const app = new Koa();
// 堆代码 duidaima.com
// 假设数据需要 5 秒的时间来获取
renderAsyncString = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('<h1>Hello World</h1>');
}, 5000);
})
}
app.use(async (ctx, next) => {
ctx.type = 'html';
ctx.body = await renderAsyncString();
await next();
});
app.listen(3000, () => {
console.log('App is listening on port 3000');
});
这是一个简化的业务场景,运行之后,会出现长达 5 秒的白屏,然后才显示出"Hello World"这段文字。毫无疑问,没有用户会愿意忍受一个长达 5 秒的白屏网页!在 web.dev[1] 对于 TTFB(Time To First Byte,首字节时间)的介绍中提到,加载第一个字节的时间应当控制在 800ms 以内,才能称得上是优质的 Web 网站服务。const Koa = require('koa');
const app = new Koa();
const Stream = require('stream');
// 假设数据需要 5 秒的时间来获取
renderAsyncString = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('<h1>Hello World</h1>');
}, 5000);
})
}
app.use(async (ctx, next) => {
const rs = new Stream.Readable();
rs._read = () => {};
ctx.type = 'html';
rs.push('<h1>loading...</h1>');
ctx.body = rs;
renderAsyncString().then((string) => {
rs.push(`<script>
document.querySelector('h1').innerHTML = '${string}';
</script>`);
})
});
app.listen(3000, () => {
console.log('App is listening on port 3000');
});
采用流式渲染后,页面最初会显示"loading...",然后在 5 秒后更新为"Hello World"。需要特别注意的是,Safari 浏览器对于何时触发流式传输可能存在一些限制(以下内容未找到官方说明,而是通过实践总结得出):
声明式 Shadow DOM,不依赖 javascript 实现
在上述的代码中,我们运用了一定的 JavaScript 代码。本质上,我们需要预先渲染一部分 HTML 标签作为占位,随后再用新的 HTML 标签对其进行替换。使用 JavaScript 来实现这一过程相对容易,但如果禁用了 JavaScript 呢? <template shadowrootmode="open">
<header>Header</header>
<main>
<slot name="hole"></slot>
</main>
<footer>Footer</footer>
</template>
<div slot="hole">插入一段文字!</div>
从中可以清晰地看到,我们的文字成功插入到了 slot 标签之中。利用声明式 Shadow DOM,我们能够对之前的示例进行改写:const Koa = require('koa');
const app = new Koa();
const Stream = require('stream');
// 假设数据需要 5 秒的时间来获取
renderAsyncString = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('<h1>Hello World</h1>');
}, 5000);
})
}
app.use(async (ctx, next) => {
const rs = new Stream.Readable();
rs._read = () => {};
ctx.type = 'html';
rs.push(`
<template shadowrootmode="open">
<slot name="hole"><h1>loading</h1></slot>
</template>
`);
ctx.body = rs;
renderAsyncString().then((string) => {
rs.push(`<h1 slot="hole">${string}</h1>`);
rs.push(null);
})
});
app.listen(3000, () => {
console.log('App is listening on port 3000');
});
运行这段改写后的代码,其结果与之前完全相同。更为重要的是,即便我们禁用了浏览器的 JavaScript,代码依然能够正常运行!声明式 Shadow DOM 是一个相对较新的特性,您可以在这篇文档[3]中获取更多详细信息。import { Suspense } from 'eact'
const renderAsyncString = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello World!');
}, 5000);
})
}
async function Main() {
const string = await renderAsyncString();
return <h1>{string}</h1>
}
export default async function App() {
return (
<Suspense fallback={<h1>loading...</h1>} >
<Main />
</Suspense>
)
}
运行这段代码,其效果与之前的示例完全一致,并且同样无需运行任何客户端的 JavaScript 代码。关于 React 的流式渲染,您可以在官方的技术层面[4]解释中获取更深入的信息。在本文中,仅作为对流式渲染的概要介绍,不对其进行更为细致的讲解。