序言
以前写过一篇文章阐述了 React 18 SSR 新架构设计,今天要鉴于此架构设计实战演练一下。假定我们自己的业务背景如下所示:
大家页面分成两大块,上边部分为个人简介,依靠插口 /api/profile
(用时约 3s),下边部分为文章列表,依靠插口 /api/list
(用时约 6s)。在其中文章列表的领域模型特别重,编码容积非常大,依靠的插口较慢。
我们首先来看一下传统 SSR (服务器端掌握到全部接口数据后启用 renderToString
3D渲染出具体内容回到给前面,另外在页面上插进全局性 INITIAL_STATE 供手机客户端灌水)和根据 Stream Rendering & Suspense for Data Fetching (下称 Stream SSR)二者的实际效果比照:
传统式 SSR | Stream SSr |
---|---|
最先,我们一起来比较一下从客户进行要求到用户见到具体内容这一阶段。传统式 SSR 客户看到的就是一个空白页面,一直要等最费时的 /api/list
接口返回客户才能看清具体内容。而 Stream SSR 主要有以下几个的提高:
- 在网页内容回到上有 loading 提示
Profile
内容先处理完毕,先回到,没被List
堵塞
相同的,灌水全过程亦是如此。传统式 SSR 必须直到 JS 载入完了,统一对整个运用开展灌水。而 Stream SSR 则先实现了 Profile
的灌水。
那样,该怎么完成这种效果呢?下面使我们 step by step。或直接看代码。
React SSR Stream Rendering & Suspense for Data Fetching 实践活动
Stream Rendering
最先,要实现 Stream Rendering,我们应该应用 renderToPipeableStream
,假定大家有以下 HTML 模版:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SSR MicroFrontend</title>
</head>
<body>
<div id="app1"><!-- app1 --></div>
<script crossorigin src="http://localhost:8080/dist/client.js"></script>
</body>
</html>
则我们能按如下所示形式进行回到:
app.get('/', async (req, res) => {
const [heal, tail] = html.split('<!-- app1 -->')
const stream = new Writable({
write(chunk, _encoding, cb) {
res.write(chunk, cb)
},
final() {
res.end(tail)
},
})
const {pipe} = renderToPipeableStream(<App />, {
onShellReady() {
res.statusCode = 200
res.write(head)
pipe(stream)
},
})
})
看见有点不对劲对吧,主要是因为 renderToPipeableStream
的回到再也不是 Node.js 里的 ReadableStream
目标,没法监视 end
事情。所以在这里通过一个中间 Writable
目标来连接数据信息,并检测3D渲染流完毕。
Stream Rendering 的那一部分搞定,下面我们看一下 Data Fetching 一部分。
Suspense for Data Fetching
在这篇文章以前讲过融合 Suspense
做 Data Fetching,但之前是本人达到的一个简单的要求专用工具,为了能更符合具体,此次应用 react-query
。则部件中可以按如下所示方法来请求数据:
async function getList() {
const rsp = await fetch('http://localhost:9000/api/list')
const data = await rsp.json()
return data
}
const List = () => {
const query = useQuery(['list'], getList)
return (
<ul>
{query.data.map((item) => (
<li key={item.name}>{item.name}</li>
))}
</ul>
)
}
使用该元件的情况下,可以使用 Suspense
包裹住,这样有利于数据信息回到前用户可看到一个 loading 效果:
const App = () => {
return (
<div>
<Suspense fallback={<p>Loading List...</p>}>
<List />
</Suspense>
...
</div>
)
}
与此同时为了降低通道文档体积,我们可以通过多线程的方式去引进 List
这一较大的部件:
const List = React.lazy(() => import('./List'))
相似的,Profile
部件也可以按一样的方式去解决。
那样,Stream Rendering & Suspense for Data Fetching 大部分算得上完成了。但是现在还有一个难题,对于每一个部件,我们也会先后在服务器端和手机客户端都要求一次插口。正确做法应当是只能在服务器端要求一次,随后服务器端回到 HTML 时把接口数据也一并携带,做为 CSR 的原始数据信息。
React Query 官方网站含有详细介绍 SSR 相关的信息,但跟传统 SSR 没什么差别,都是要等数据获得完了,才3D渲染:
function handleRequest (req, res) {
const queryClient = new QueryClient()
await queryClient.prefetchQuery('key', fn)
const dehydratedState = dehydrate(queryClient) // 得到一个接口请求的全局状态
const html = ReactDOM.renderToString(
<QueryClientProvider client={queryClient}>
<Hydrate state={dehydratedState}>
<App />
</Hydrate>
</QueryClientProvider>
)
res.send(`
<html>
<body>
<div id="root">${html}</div>
<script>
window.__REACT_QUERY_STATE__ = ${JSON.stringify(dehydratedState)};
</script>
</body>
</html>
`)
queryClient.clear()
}
这样的行为几个缺陷:
- 全部运用的3D渲染都已经被堵塞了,本来可以较早返回
Profile
也被顶迟了 - 务必要记住现阶段页面渲染所要调用的全部插口,当网页页面很复杂和由多的人维护保养时这一编码就不太好保护了
下面我们就来解决这个问题,最后的计划方案我称作“全局状态滚动更新”计划方案。
全局状态滚动更新
从上述编码就可以知道,根据 dehydrate(queryClient)
可以获得一个全局性目标用于叙述现阶段要求获得的信息,那么我们是否可以在部件里边每一次有数据收集到时候就要来更新一下这一目标呢?就像这样:
const query = useQuery(['data'], getList)
const ee = useContext(EventEmitterContext)
if (ee && query.data) {
ee.emit('updateState')
}
最后我们在对待请求调用函数中监视这件事情,升级全局状态:
const templateDOM = new JSDOM(`
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<div id="app1"><!-- app1 --></div>
<script id="reactQueryState">window.__REACT_QUERY_STATE__ = ${JSON.stringify(
dehydratedState
)};</script>
...
</body>
</html>
`)
...
ee.on('updateState', () => {
const dehydratedState = dehydrate(queryClient)
templateDoc.querySelector(
'#reactQueryState'
).innerHTML = `window.__REACT_QUERY_STATE__ = ${JSON.stringify(
dehydratedState
)};`
})
这样大家就做到了极致依然流式的的回到具体内容给消费者,并在这过程中不断地升级全局性数据信息,最终回到给手机客户端,而且还不要了解这一页面渲染所需的全部插口,即确保了客户体验,又没遗失程序代码可扩展性。
React 精英团队一直在提升用户体验、编码可扩展性和性能这种上进行不断地探寻,觉得在不久的将来核心技术的开发方法又会被她们影响了,看起来前面都还没死,还可以继续瞎折腾。
写文章不容易,如果你觉得文章内容也有帮助,不便一键三连,请关注“前面游”微信公众号
本站是一个以CSS、JavaScript、Vue、HTML为中心的前端开发技术网址。我们的使命是为众多前端工程师者提供全方位、全方位、好用的前端工程师专业知识和技术服务。 在网站上,大家可以学到最新前端开发技术,掌握前端工程师最新发布的趋势和良好实践。大家提供大量实例教程和实例,让大家可以快速上手前端工程师的关键技术和程序。 本站还提供了一系列好用的工具软件,帮助你更高效地开展前端工程师工作中。公司提供的一种手段和软件都要经过精心策划和改进,能够帮助你节约时间精力,提高研发效率。 此外,本站还拥有一个有活力的小区,你可以在社区里与其它前端工程师者沟通交流技术性、交流经验、处理问题。我们坚信,街道的能量能够帮助你能够更好地进步与成长。 在网站上,大家可以寻找你需要的一切前端工程师网络资源,使您成为一名更加出色的网页开发者。欢迎你添加我们的大家庭,一起探索前端工程师的无限潜能!代办报建
本公司承接江浙沪报建代办施工许可证。
联系人:张经理,18321657689(微信同号)。
12条评论
小弟默默的路过贵宝地~~~http://xqwtr.jobdq.com/07/4.html
坚持回帖!http://rfdb.bmw8294.com
感谢楼主的推荐!http://29i0m.att-sp.com
感谢楼主的推荐!http://zzj5.scalecn.com
对牛弹琴的人越来越多了!http://t26aq.yhtgcl2.com
这篇文章真是让人受益匪浅!http://b5tb.506408.com
楼主看起来很有学问!http://www.3553km.com
帖子很有深度!http://8vl83.jssg888.com
世界末日我都挺过去了,看到楼主我才知道为什么上帝留我到现在!http://n6g.hmchwgt.com
有钱、有房、有车,人人都想!http://jydc.lssdkc.com
小弟默默的路过贵宝地~~~http://www.guangcexing.net/voddetail/FqvsehntXz.html
白富美?高富帅?https://www.skypeis.com/
发表评论