性能优化之组件懒加载: Vue Lazy Component 介绍

这篇文章分享了从遇到前端业务性能问题,到分析解决而且梳理通用的Vue 2.x 组件级懒加载解决方案(Vue Lazy Component )的过程。css

初始加载资源过多

问题起源于咱们的一个页面,下面是这个页面的截图和初次请求的瀑布图。html

初始加载了155个请求
初始加载了155个请求

初始加载的时候,一共请求了155个资源,请求的瀑布图就快要和页面同样长了😊前端

请求概况
请求概况

初始加载的资源过多致使在 domInteractive 以后,页面花费了大量时间加载子资源,致使页面的 load 时长被严重拖长,达到了 5.6s 。vue

PerformanceNavigationTiming 信息
PerformanceNavigationTiming 信息

来看看这些子资源都是什么,根据请求资源的类型,咱们找到了最多的类型是图片,这是显而易见的,页面上处处都是大图片,其次是 js 文件,由第三方的业务插件和一些 JSONP 的接口组成。webpack

请求资源类型分布
请求资源类型分布

问题分析

页面结构拆分
页面结构拆分

再回到最初的这个页面,结合上面的数据状况咱们得出了这个页面的问题总结结论:git

  • 页面由大量模块组成
  • 每一个模块部分由首页自主维护,部分由业务方经过插件维护
  • 全部模块是同时进行加载
  • 模块中图片内容较多
  • 每一个模块的依赖资源较多(包括js文件、接口文件、css文件等)

解决思路

咱们提出了下面两个主要的解决思路:github

组件化分治思想

为了方便后续的优化,咱们必需要求每一个模块之间下降耦合,将相关的逻辑(好比请求接口、请求相关的依赖资源)都封装在内部,在 Vue 里落实成组件的形式。web

  • 将各模块拆分为组件粒度
  • 将组件依赖的资源所有封装在组件内部进行调用

加载优先级

在完成了组件化的拆分,确保模块之间不会互相影响和产生耦合以后,咱们能够方面地调整加载策略。加载的策略是根据可见性来处理优先级问题。npm

  • 优先加载首屏可见模块
  • 其他不可见模块懒加载,待可见或即将可见时加载

有了上面的解决思路,咱们开始思考具体的实现:浏览器

如何解决判断可见性问题?

从前咱们都是经过监听滚动事件、resize 事件来判断模块是否可见,代码不只繁琐,并且一不当心没有函数去抖就又可能致使严重的性能问题。

如今咱们有了更好的选择—— IntersectionObserver API ,IntersectionObserver 容许你配置一个回调函数,每当 target ,元素和设备视口或者其余指定元素发生交集的时候该回调函数将会被执行。这个 API 的设计是异步的,并且保证你的回调执行次数是很是有限的,并且回调是会在主线程空闲时才执行,在性能方面表现更优,使用起来也更简单。

http://caniuse.com/#search=IntersectionObserver
http://caniuse.com/#search=IntersectionObserver

目前是现代浏览器支持,低版本浏览器能够经过 polyfill 兼容。

如何尽量懒的条件渲染?

在解决了加载条件的判断以后,咱们须要解决加载条件为假的状况下不去渲染、加载条件为真的时候才渲染的问题,这里的答案很是简单:使用 Vue.js 提供的 v-if 指令,就能够作到真正的惰性渲染。

若是可见后进行初始渲染,可见前如何显示?

若是在判断加载条件为假的时候,什么都不渲染,就会带来一系列问题:

  • 用户体验比较差,最开始是白屏,而后忽然又渲染出现内容。
  • 最致命的是咱们判断可见性是须要一个目标来观察的,若是什么不都渲染,咱们就无从观察。

这里引入一个骨架屏的概念,咱们为真实的组件作一个在尺寸、样式上很是接近真实组件的组件,叫作骨架屏。

骨架屏
骨架屏

骨架屏的做用有:

  • 提高用户感知体验
  • 保证切换的一致性
  • 提供可见性观察的目标对象

如何提高切换时的体验?

在真实组件开始渲染的时候,须要必定的时间和空间,时间指的是真实组件从建立到渲染的时间,包括请求接口、请求资源和渲染的时间,空间指的是页面布局中须要给真实组件留出恰好的位置,避免产生抖动。

这里咱们可使用 Vue.js 内置的 transition 组件自定义骨架组件和真实组件的进入和离开效果,经过合理的布局和定位,减小切换时的抖动,
经过设置过渡效果给真实组件留出必定的加载时间。

上面的问题都有了答案以后,咱们很容易就能够实现一个通用的方案,来解决组件的懒加载问题。

Vue组件懒加载方案介绍

项目Github地址: github.com/xunleif2e/v…

这个是咱们基于上面的思考作的一个通用的解决方案,下面简单介绍一下特性、使用以及 API 方面的知识,后面结合 5 个具体的 DEMO 来说解更高级的用法。

特性

  • 支持 组件可见或即将可见时懒加载
  • 支持 组件延时加载
  • 支持 加载组件前展现组件骨架,提升用户体验
  • 支持 懒加载组件分包异步加载

安装和使用

npm i @xunlei/vue-lazy-component复制代码
  • 方式1 利用插件方式全局注册
  • 方式2 局部注册
  • 方式3 独立版本引入,自动全局注册

用法

使用方式
使用方式

Props

参数 说明 类型 可选值 默认值
viewport 组件所在的视口,若是组件是在页面容器内滚动,视口就是该容器 HTMLElement true null,表明视窗
direction 视口的滚动方向, vertical表明垂直方向,horizontal表明水平方向 String true vertical
threshold 预加载阈值, css单位 String true 0px
tagName 包裹组件的外层容器的标签名 String true div
timeout 等待时间,若是指定了时间,不论可见与否,在指定时间以后自动加载 Number true -

Events

事件名 说明 事件参数
before-init 模块可见或延时截止致使准备开始加载懒加载模块 -
init 开始加载懒加载模块,此时骨架组件开始消失 -
before-enter 懒加载模块开始进入 el
before-leave 骨架组件开始离开 el
after-leave 骨架组件已经离开 el
after-enter 懒加载模快已经进入 el
after-init 初始化完成 -

DEMO 1 超长页面懒加载

xunleif2e.github.io/vue-lazy-co…

<vue-lazy-component>
    <st-series-sohu/>
    <st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>复制代码

经过上面这种简单的使用方式就能够实现组件即将可见时自动加载。

DEMO 2 延时加载

xunleif2e.github.io/vue-lazy-co…

延时加载
延时加载

<vue-lazy-component :timeout="1000">
    <st-series-sohu/>
    <st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>
`复制代码

若是有时候仅仅是但愿某些组件稍后渲染,而不必定要等到可见时,能够经过这种方式。

好比咱们业务中可能会有些运营性质的挂件,就能够采起延时加载的方式。

DEMO 3 自定义过渡效果

xunleif2e.github.io/vue-lazy-co…

自定义过渡效果
自定义过渡效果

若是以为 Vue Lazy Component 自带的淡入淡出的过渡效果太丑,或者须要调整淡入淡出效果的时长,就能够经过自定义样式来改变过渡效果。这个例子演示了另一种过渡效果,transition 的生命周期能够参考 Vue.js 的 transition 组件的文档。

DEMO 4 webpack 分包

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 对组件的解析机制有关,例子里有相应的的代码,有兴趣的同窗能够深刻研究下。

DEMO 5 特定视口内懒加载

xunleif2e.github.io/vue-lazy-co…

IM场景
IM场景

在某些场景下,咱们要解决滚动容器内的组件懒加载,这个时候可见性是相对与这个视口来的,这个例子演示了如何指定聊天窗口做为观察的视口。

这里吐槽下Vue.js的 $parent 、$refs 的设计,它们都不是响应式的,若是须要动态获取这些组件引用上的 $el ,必需要等到 mounted 事件发生以后,因此例子的代码稍微有一点繁琐。

应用效果

首先 Vue Lazy Component 的设计虽然是说组件级的,其实它的粒度可大可小,大的好比页面不一样的区域,小的就像 DEMO5 里的只是一个用户头像,因此适用性很是强,只要有懒加载需求的场景基本均可以采用。

另外,在终端方面,不只能够兼容PC端的项目,在移动端也是可使用的,固然,须要解决 IntersectionObserver API 的兼容性问题,在项目 Readme 里提到了 w3c 的 polyfill 的地址。

应用业务

咱们目前应用在迅雷的两个项目中,一个是 PC 迅雷的首页项目,一个是 PC 迅雷的组队加速项目,后期预计会推广到更多的业务中去。

迅雷前端项目
迅雷前端项目

优化后请求瀑布图

咱们再来看看开始那个页面的状况,在使用了 组件懒加载技术后,请求数变成了只有 31 个,瀑布图变得比较短了。

请求数变为31个
请求数变为31个

数据对比

咱们把先后的数据进行一个对比:

  • 请求数变成以前的 1 / 5,优化效果比较明显
  • 请求大小相比以前下降不太明显
  • load 时长也一样不太明显

分析主要是有较多图片未按照使用尺寸裁剪和压缩,致使请求大小较大,同时形成了 load 时长的拖长。

后续性能优化方向

后续咱们会继续来优化这个页面,主要的方向有两个:

图片尺寸适配和压缩

经过图片的裁剪和压缩,解决请求资源大小较大子资源加载时间较长致使 load 时间拖长的问题

预渲染

采用预渲染插件将页面的主要 css 、js 进行内联,将骨架架屏经过预渲染生成出来,这样能够避免 SPA 首屏可见关键路径较长的问题,在页面解析完 dom 树之后便可保证首屏可见。

懒加载方案 ROADMAP

Vue Lazy Component 懒加载方案还有些地方作得还不够好,计划在后期的几个小版本里支持如下的特性:

  • SSR 支持 v1.1.0
  • UI单元测试 v1.2.0
  • 减小性能开销 v1.3.0
    • 重绘
    • FPS

后记

这篇文章分享了从遇到业务实际性能问题,到分析、解决并梳理出通用的解决方案的过程,重点其实不是最终的实现代码实现,而是解决问题的角度和过程。

最后欢迎你们经过提交 issue 或者 PR 的方式参与贡献,项目 Github 地址: github.com/xunleif2e/v…


扫一扫关注迅雷前端公众号

相关文章
相关标签/搜索