本文为原创文章,引用请注明出处,欢迎大家收藏和分享💐💐
背景
如今的前端技术层出不穷,无论是react
、vue
等框架还是跨端解决方案,为使用场景和开发效率做了不少的提升,但作为前端技术的重要衡量指标之一,首屏渲染效率无疑前端老生常谈的话题了。这篇文章就来聊下如何在常见的H5环境下,做到页面秒开。
业务场景
这里也是引用笔者之前做过的一个业务来举例:有一个模拟用户朋友圈记录的H5页面,用户能通过管理端来编辑一条朋友圈消息「图文|视频」,并展示在这个H5页面上。如下图:
在这个场景下,如何快速打开页面并把朋友圈动态展示出来就显得尤为重要了,因为首屏速度越快,有效曝光率就越高、挽留率也就越高,如何提高页面的访问速度可以说是这个业务的硬性指标。
另外,这个业务还有另外的技术要求:
- 实时更新,与后台管理系统操作同步(后台新增1条消息前端就要有新记录);
- 秒开,最好能达到native页面的体验感。
秒开的技术探讨
网页请求流程
在确定方案前,我们先回顾下网页的请求全流程:
part1:浏览器发起document请求
- app cache:检查域名缓存,如果有缓存就不需要DNS解析域名;
- DNS解析:把域名解析成IP;
- TCP连接:浏览器发起TCP连接请求。经过标准的TCP握手流程,建立TCP连接;
- HTTP请求:按照HTTP协议标准发送一个索要网页的请求;
- API网关转发:一般服务配备业务转发能力,根据不同路径转发到不同服务;
- 负载均衡:计算负载,转发到一台后端的真实Web服务器,Web服务器收到请求,产生响应,并将网页返回。
part2:document到页面渲染
- 根据 HTML 结构生成
DOM Tree
; - 根据 CSS 生成
CSSOM
; - 将 DOM 和 CSSOM 整合形成
RenderTree
; - 根据 RenderTree 开始渲染和展示;
- 遇到script标签时,会执行并阻塞渲染,因为
Javascript
代码有权利改变DOM树; - 异步请求触发,完善页面数据,最终得到一个最终页面。
由此看来,对于首屏的常规优化,我们可以采取资源压缩&合并、cdn加速、骨架图等一系列措施,这都是老生常谈的优化方案了;
其实,对于动态页面,往往需要在onload后发起额外的异步请求(上述第6步),在这个过程中,会有或多或少的等待时间,降低用户体验。
思考:有没有办法让这类页面提前渲染出最终形态??
没错,我们可以采用ssr渲染方案(即是在part1过程进行数据提前处理),在请求html的时候在网关层进行拦截,转发到后台服务把数据写入html,把最终带有数据的页面返回给前端,流程图如下:
这是常规的SSR渲染方案,只是异步数据拉取时机由前端调用改为服务端调用。按理说,这时候的:首屏时长=服务请求时长+服务获取异步数据时长+浏览器渲染页面时长。
虽然说服务器拉数据比前端更稳定和快速,但带来了额外的问题:
- 拉取数据服务宕机,导致html请求阻塞,前端页面一直处于空白等待状态,需要服务端做额外逻辑兼容;
- 拉取数据耗时较长时,前端页面的白屏时间也相应增加,不但没有实现秒开效果,反而拖慢页面加载速度;
思考plus:有没有办法在实现SSR情况下又能保证页面秒开?
这样,我们再想想在哪个流程点可以优化下:
- 放弃ssr,从优化前端资源入手
- ssr+本地存储
- 设置ssr数据拉取接口超时,前端页面onload后加上ajax请求补偿
- node服务+redis数据存储,代替额外的数据请求
方案对比
放弃ssr,从优化前端资源入手
- 在 HTML 内实现 Loading 态或者骨架屏;
- 去掉外联 css;
- 使用动态 polyfill;
- 使用 SplitChunksPlugin 拆分公共代码;
- 正确地使用 Webpack 4.0 的 Tree Shaking;
- 使用动态 import,切分页面代码,减小首屏 JS 体积;
- 编译到 ES2015+,提高代码运行效率,减小体积;
- ...
缺点:无法抹平异步数据加载带来的页面抖动,但可以快速给用户呈现页面雏形,综合考虑无法满足需求,舍弃。
ssr+前端本地存储
使用localstorage对首次请求得到的数据缓存,并设置有效时间,在有效期内直接读取本地数据...
缺点:无法保证数据实时性,无法满足需求,舍弃。
设置ssr数据拉取api超时,前端页面onload后加上ajax请求补偿
这个就是在服务器拉取数据时加上短暂的时间判断,在接口超时情况下直接返回没有ssr渲染的页面,前端在首屏完成后再异步请求数据。
分析:服务器之间的请求相对比较稳定而高效,ssr成功率也相对比较高,可以采取。
改后的流程如下:
node服务+redis数据存储,代替额外的数据请求「推荐」
这方案大致思路:admin在管理后台新增朋友圈记录时,顺便拉取该用户最近20条记录,并把它们写进redis中。之后在H5请求数据时,先进redis检查是否有用户记录,有就直接写ssr并返回document,达到极速渲染效果。
一般对于数据量不是很大的请求,http在跨服务上的请求平均耗时100+ms起,而redis能达到10+ms的级别,在这2种方式,效率差别尤为明显。
技术栈:nodejs、react、redis、ReactDOMServer
整理后的流程如下:
redis具备高性能的特点,参考资料《redis高性能原理》
ReactDOMServer可以参考官方描述,主要作用是在服务端将react函数实例化成一个dom
Ajax vs Redis 效率
Ajax
对于异步获取数据的http请求开销:
Redis
使用nodejs+redis ssr处理耗时:
{"msg":"开始处理demo_user的ssr渲染...","time":"2021-04-11T03:36:45.842Z","v":0} {"msg":"redis数据读取成功,共20条数据","time":"2021-04-11T03:36:45.858Z","v":0} {"msg":"ssr渲染处理成功","time":"2021-04-11T03:36:45.869Z","v":0}
浏览器视角,获取html文档流开销:
整个数据获取+处理过程大约只需要27ms,而首屏完全加载时间也保证在329ms,对比起来,我们在请求html文档时,在服务器直接对redis读取数据并写入ssr,效率提高了不止一个档次。
那如何保证redis数据是最新的?其实也很简单,在对用户数据进行数据库操作同时,更新一份到redis就可以了,而且ssr用于首屏渲染只需要前20条数据,固redis保存的数据量是可控的。当然,redis也不是绝对可靠的,所以我们还需要做些补偿方案,例如在redis获取数据失败时,改调用接口获取数据等。
效果展示
ssr秒开方案
普通异步加载
写在最后
欢迎大家关注本人公众号「是马非马」,一起玩耍起来!🌹🌹