这篇文章分享了从遇到前端业务性能问题,到分析、解决而且梳理出通用的Vue 2.x 组件级懒加载解决方案(Vue Lazy Component )的过程。css
问题起源于咱们的一个页面,下面是这个页面的截图和初次请求的瀑布图。html
初始加载的时候,一共请求了155个资源,请求的瀑布图就快要和页面同样长了😊前端
初始加载的资源过多致使在 domInteractive 以后,页面花费了大量时间加载子资源,致使页面的 load 时长被严重拖长,达到了 5.6s 。vue
来看看这些子资源都是什么,根据请求资源的类型,咱们找到了最多的类型是图片,这是显而易见的,页面上处处都是大图片,其次是 js 文件,由第三方的业务插件和一些 JSONP 的接口组成。webpack
再回到最初的这个页面,结合上面的数据状况咱们得出了这个页面的问题总结结论:git
咱们提出了下面两个主要的解决思路:github
为了方便后续的优化,咱们必需要求每一个模块之间下降耦合,将相关的逻辑(好比请求接口、请求相关的依赖资源)都封装在内部,在 Vue 里落实成组件的形式。web
在完成了组件化的拆分,确保模块之间不会互相影响和产生耦合以后,咱们能够方面地调整加载策略。加载的策略是根据可见性来处理优先级问题。npm
有了上面的解决思路,咱们开始思考具体的实现:浏览器
从前咱们都是经过监听滚动事件、resize 事件来判断模块是否可见,代码不只繁琐,并且一不当心没有函数去抖就又可能致使严重的性能问题。
如今咱们有了更好的选择—— IntersectionObserver API ,IntersectionObserver 容许你配置一个回调函数,每当 target ,元素和设备视口或者其余指定元素发生交集的时候该回调函数将会被执行。这个 API 的设计是异步的,并且保证你的回调执行次数是很是有限的,并且回调是会在主线程空闲时才执行,在性能方面表现更优,使用起来也更简单。
目前是现代浏览器支持,低版本浏览器能够经过 polyfill 兼容。
在解决了加载条件的判断以后,咱们须要解决加载条件为假的状况下不去渲染、加载条件为真的时候才渲染的问题,这里的答案很是简单:使用 Vue.js 提供的 v-if 指令,就能够作到真正的惰性渲染。
若是在判断加载条件为假的时候,什么都不渲染,就会带来一系列问题:
这里引入一个骨架屏的概念,咱们为真实的组件作一个在尺寸、样式上很是接近真实组件的组件,叫作骨架屏。
骨架屏的做用有:
在真实组件开始渲染的时候,须要必定的时间和空间,时间指的是真实组件从建立到渲染的时间,包括请求接口、请求资源和渲染的时间,空间指的是页面布局中须要给真实组件留出恰好的位置,避免产生抖动。
这里咱们可使用 Vue.js 内置的 transition 组件自定义骨架组件和真实组件的进入和离开效果,经过合理的布局和定位,减小切换时的抖动,
经过设置过渡效果给真实组件留出必定的加载时间。
上面的问题都有了答案以后,咱们很容易就能够实现一个通用的方案,来解决组件的懒加载问题。
项目Github地址: github.com/xunleif2e/v…
这个是咱们基于上面的思考作的一个通用的解决方案,下面简单介绍一下特性、使用以及 API 方面的知识,后面结合 5 个具体的 DEMO 来说解更高级的用法。
npm i @xunlei/vue-lazy-component复制代码
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
viewport | 组件所在的视口,若是组件是在页面容器内滚动,视口就是该容器 | HTMLElement | true | null ,表明视窗 |
direction | 视口的滚动方向, vertical 表明垂直方向,horizontal 表明水平方向 |
String | true | vertical |
threshold | 预加载阈值, css单位 | String | true | 0px |
tagName | 包裹组件的外层容器的标签名 | String | true | div |
timeout | 等待时间,若是指定了时间,不论可见与否,在指定时间以后自动加载 | Number | true | - |
事件名 | 说明 | 事件参数 |
---|---|---|
before-init | 模块可见或延时截止致使准备开始加载懒加载模块 | - |
init | 开始加载懒加载模块,此时骨架组件开始消失 | - |
before-enter | 懒加载模块开始进入 | el |
before-leave | 骨架组件开始离开 | el |
after-leave | 骨架组件已经离开 | el |
after-enter | 懒加载模快已经进入 | el |
after-init | 初始化完成 | - |
xunleif2e.github.io/vue-lazy-co…
<vue-lazy-component>
<st-series-sohu/>
<st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>复制代码
经过上面这种简单的使用方式就能够实现组件即将可见时自动加载。
xunleif2e.github.io/vue-lazy-co…
<vue-lazy-component :timeout="1000">
<st-series-sohu/>
<st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>
`复制代码
若是有时候仅仅是但愿某些组件稍后渲染,而不必定要等到可见时,能够经过这种方式。
好比咱们业务中可能会有些运营性质的挂件,就能够采起延时加载的方式。
xunleif2e.github.io/vue-lazy-co…
若是以为 Vue Lazy Component 自带的淡入淡出的过渡效果太丑,或者须要调整淡入淡出效果的时长,就能够经过自定义样式来改变过渡效果。这个例子演示了另一种过渡效果,transition 的生命周期能够参考 Vue.js 的 transition 组件的文档。
xunleif2e.github.io/vue-lazy-co…
DEMO1 演示了如何懒加载模块,但其实只是推迟了模块的渲染和模块内的资源的加载,若是咱们须要更进一步,连模块自己的代码也是懒加载,就像 AMD 那样异步按需加载,这个也是能够作到的。
这里能够利用Vue.js的异步组件,将每一个真实组件都注册成异步组件,在异步组件的工厂函数里使用 Webpack 的 AMD 版本的 require ,就能够实现真实组件能够分红独立的 bundle 加载,脱离页面 js 的bundle。
可是这里会有个问题,就算模块是可见时才渲染,在打开页面的时候会发现模块不可见以前它的 bundle 已经加载了,这并无实现按需加载。
这个例子演示了一种作法,Vue Lazy Component 能够在即将切换真实组件前经过 Scoped Slots 传递一个 loading 属性给真实组件,真实组件只要是根据这个 loading 来条件渲染就能够避免非按需加载,这个和 Vue.js 对组件的解析机制有关,例子里有相应的的代码,有兴趣的同窗能够深刻研究下。
xunleif2e.github.io/vue-lazy-co…
在某些场景下,咱们要解决滚动容器内的组件懒加载,这个时候可见性是相对与这个视口来的,这个例子演示了如何指定聊天窗口做为观察的视口。
这里吐槽下Vue.js的 $parent 、$refs 的设计,它们都不是响应式的,若是须要动态获取这些组件引用上的 $el ,必需要等到 mounted 事件发生以后,因此例子的代码稍微有一点繁琐。
首先 Vue Lazy Component 的设计虽然是说组件级的,其实它的粒度可大可小,大的好比页面不一样的区域,小的就像 DEMO5 里的只是一个用户头像,因此适用性很是强,只要有懒加载需求的场景基本均可以采用。
另外,在终端方面,不只能够兼容PC端的项目,在移动端也是可使用的,固然,须要解决 IntersectionObserver API 的兼容性问题,在项目 Readme 里提到了 w3c 的 polyfill 的地址。
咱们目前应用在迅雷的两个项目中,一个是 PC 迅雷的首页项目,一个是 PC 迅雷的组队加速项目,后期预计会推广到更多的业务中去。
咱们再来看看开始那个页面的状况,在使用了 组件懒加载技术后,请求数变成了只有 31 个,瀑布图变得比较短了。
咱们把先后的数据进行一个对比:
分析主要是有较多图片未按照使用尺寸裁剪和压缩,致使请求大小较大,同时形成了 load 时长的拖长。
后续咱们会继续来优化这个页面,主要的方向有两个:
经过图片的裁剪和压缩,解决请求资源大小较大子资源加载时间较长致使 load 时间拖长的问题
采用预渲染插件将页面的主要 css 、js 进行内联,将骨架架屏经过预渲染生成出来,这样能够避免 SPA 首屏可见关键路径较长的问题,在页面解析完 dom 树之后便可保证首屏可见。
Vue Lazy Component 懒加载方案还有些地方作得还不够好,计划在后期的几个小版本里支持如下的特性:
这篇文章分享了从遇到业务实际性能问题,到分析、解决并梳理出通用的解决方案的过程,重点其实不是最终的实现代码实现,而是解决问题的角度和过程。
最后欢迎你们经过提交 issue 或者 PR 的方式参与贡献,项目 Github 地址: github.com/xunleif2e/v… 。