解耦 menu 逻辑与内容区域逻辑, 加快页面渲染速度。
Closed this issue · 1 comments
MuYunyun commented
背景
当前内容区由预渲染吐出首屏,但是随后加载 JavaScript 逻辑后,页面重新 loading 载入导致预渲染的页面「被浪费」。究其原因,其中涉及到变动的逻辑仅仅是在菜单区域,但是照成了全部的页面的重新 loading 是不可接受的。因此可以解耦菜单与内容区,在利用预渲染带来的首屏体验的同时,仅仅重新渲染需要加载动态逻辑的部分,进一步完善衔接体验。
- 问题演示
MailVideo.mov
以访问章节 快速上手 为例,用户从访问到首屏渲染页面到页面可交互(JavaScript 逻辑执行完毕),会经历如下步骤:
首屏阶段:当用户访问 快速上手 时,gp-pages 推送预渲染页面,用户获得友好的首屏体验 😁。
衔接阶段:从预渲染页面到页面可交互,出现了干扰体验的加载,体验十分不好 😭。
可交互阶段:左侧菜单按钮等 JavaScript 逻辑执行完毕,在这个阶段用户可以与页面进行交互。
策略
- ❎ 思路一: 内容区域使用 iframe 单独加载, 与 menu 分离。
- 要保证内容区的预渲染,如果内容区在 iframe 中加载,会导致内容区的渲染更加延后。
- ❎ 思路二: 重构左侧 menu, 不用 menu 动画逻辑, 一个层级只显示当前 menu 列表。相关 issue:#219
- 缺陷: 该做法的交互与存量交互完全不同,成本高。
- 思路三: 菜单区域使用 iframe 单独加载, 与内容区分离。
- 缺陷:
- 牺牲部分体验: 菜单栏交互需要变更为 fixed 定位,体验没有原来的效果好。
- Iframe 区域同步事件成本高: 这部分没有进一步探究,可预计的是成本不小。
- 缺陷:
- 思路四: 从根路径解耦区分预渲染与线上环境的渲染,预渲染时渲染出大部分静态页面,线上渲染渲染补充动态页面与逻辑。同时移除「正在加载中逻辑」。
const ifProdRender = ifProd && !ifPrerender
if (!ifProdRender) {
ReactDOM.render(
<RouterRoot />,
document.getElementById('root'),
)
} else {
// render dynamic logic(such as menu) here.
ReactDOM.render(
<RouterRoot pointRender="menu" />,
document.getElementById('root'),
)
}packages/crd-seed/layout/index.js
return (
<>
{
pointRender === 'menu'
// prod render
? renderMenuContainer()
// pre & dev render
: <div className={styles.wrapper}>
<Header
logo={logo}
href={ifAddPrefix ? `/${repo}` : `/`}
location={location}
indexProps={indexProps}
menuSource={menuSource}
/>
<div
className={cx(styles.wrapperContent, {
[styles.wrapperMobile]: isMobile,
})}
>
{renderPageHeader()}
<div id="menuPosition">
{renderMenuContainer()}
</div>
{renderContent()}
<Footer inlineCollapsed={inlineCollapsed} />
</div>
</div>
}
</>
)此番修改后,首屏加载后,确实不会二次刷新了,但是带来了几个问题,
- menu 点击切换后,url 会发生变化,但是内容区未发生更新。
- 内容区(包含样式)未更新,因此不能展开收起。
当前在思路四的基础上,确实不可避免存在移除预渲染的 html 后的一刹那闪动问题。
- ✨ 思路五: 依赖 gp-pages 服务,基于预渲染加载 SSR 首屏渲染页面,然后客户端执行注水逻辑。
MailVideo.mov
- ReactDOM.render(
- <RouterRoot />,
- document.getElementById('root'),
- )
+ if (ifDev) {
+ // dev render
+ document.getElementById('root').innerHTML = ReactDOMServer.renderToString(<RouterRoot />)
+ ReactDOM.hydrate(
+ <RouterRoot />,
+ document.getElementById('root'),
+ )
+ } else if (ifPrerender) {
+ // prerender
+ document.getElementById('root').innerHTML = ReactDOMServer.renderToString(<RouterRoot />)
+ } else {
+ // prod render:
+ ReactDOM.hydrate(
+ <RouterRoot />,
+ document.getElementById('root'),
+ )
+ }stale commented
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


