性能优化
所有开发者都无法避免的一个问题,即关于项目的性能优化。性能优化是一个经久不衰的问题,它几乎贯穿于整个项目的开发过程。做好性能优化的项目不仅能在用户体验上更胜一筹,还能让服务资源的分配更加的合理。
关于SPA(单页面)应用程序的优化
前端开发者基本上都知道单页面的程序有一个通病,首屏加载太慢了,因为第一次访问的时候需要加载的东西太多,资源量太大以至于体验起来可能并不那么友好,以下就这个点便可以针对性地展开优化。
1、路由懒加载
SPA 项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验。
官方其实也就这个问题给出了答复,我们只需要按照他们的提示去改动代码,那么就可以在一定程度上去提高项目的运行速度。
// 通过webpackChunkName设置分割后代码块的名字
const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue");
const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue");
…………
const routes = [
{
path: "/",
name: "home",
component: Home
},
{
path: "/metricGroup",
name: "metricGroup",
component: MetricGroup
},
…………
]
2、组件懒加载
组件懒加载跟路由懒加载的方式一样,将资源分开,等到需要的时候我们再把它引入到我们的页面中,从而节省资源,优化页面的加载速度。
例如弹窗组件的懒加载。
<script>
const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');
export default {
name: 'homeView',
components: {
dialogInfo
}
}
</script>
重新打包后,home.js 和 about.js 中没有了弹框组件的代码,该组件被独立打包成 dialogInfo.js,当用户点击按钮时,才会去加载 dialogInfo.js 和 dialogInfo.css
组件懒加载的使用场景
有时资源拆分的过细也不好,可能会造成浏览器 http 请求的增多
总结出三种适合组件懒加载的场景:
1)该页面的 JS 文件体积大,导致页面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)
2)该组件不是一进入页面就展示,需要一定条件下才触发(比如弹框组件)
3)该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)
懒加载原理
懒加载前提的实现:ES6的动态地加载模块——import()。
要实现懒加载,就得先将进行懒加载的子模块分离出来,打包成一个单独的文件
webpackChunkName 作用是 webpack 在打包的时候,对异步引入的库代码(lodash)进行代码分割时,设置代码块的名字。webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中
简而言之,就是用懒加载后程序只会去加载你当前页面所需要的资源,不需要的资源暂时不会请求,而且在后续如果重复的资源被利用了,也不会再做请求,从而消耗了我们浏览器资源。
3、 Tree shaking的优化
tree-shaking 原理:
依赖于ES6的模块特性,ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是 tree-shaking 的基础
静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。ES6之前的模块化,比如 CommonJS 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 tree-shaking 成为可能
Tree shaking 的作用:
消除无用的 JS 代码,减少代码体积
对比以下两端代码
// 第一段
export function targetType(target) {
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
}
export function deepClone(target) {
return JSON.parse(JSON.stringify(target));
}
// 第二段
export default {
targetType(target) {
return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();
},
deepClone(target) {
return JSON.parse(JSON.stringify(target));
}
};
// 引入并使用
import util from '../util';
util.targetType(null)
其实两者在使用上并无太大的区别,只是第二段又多做了一层封装,但是这样的封装会让tree shaking失效,使项目打包体积增大,降低我们资源包的精准分配。因为我们所进行的封装会默认地把该文件中所有的函数整个被引入到组件页面中,导致一些无用代码的产生。
究其原因,export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree-shaking 只对使用 export 导出的变量生效
这也是函数式编程越来越火的原因,因为可以很好利用 tree-shaking 精简项目的体积,也是 vue3 全面拥抱了函数式编程的原因之一
4、骨架屏优化白屏时长
使用骨架屏,可以缩短白屏时间,提升用户体验。国内大多数的主流网站都使用了骨架屏,特别是手机端的项目
SPA 单页应用,无论 vue 还是 react,最初的 html 都是空白的,需要通过加载 JS 将内容挂载到根节点上,这套机制的副作用:会造成长时间的白屏
常见的骨架屏插件就是基于这种原理,在项目打包时将骨架屏的内容直接放到 html 文件的根节点中
使用骨架屏插件,打包后的 html 文件(根节点内部为骨架屏):
骨架屏插件
①安装插件
npm i vue-skeleton-webpack-plugin
②vue.config.js 配置
const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");
module.exports = {
configureWebpack: {
plugins: [
new SkeletonWebpackPlugin({
// 实例化插件对象
webpackConfig: {
entry: {
app: path.join(__dirname, './src/skeleton.js') // 引入骨架屏入口文件
}
},
minimize: true, // SPA 下是否需要压缩注入 HTML 的 JS 代码
quiet: true, // 在服务端渲染时是否需要输出信息到控制台
router: {
mode: 'hash', // 路由模式
routes: [
// 不同页面可以配置不同骨架屏
// 对应路径所需要的骨架屏组件id,id的定义在入口文件内
{ path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' },
{ path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' }
]
}
})
]
}
}
③新建 skeleton.js 入口文件
// skeleton.js
import Vue from "vue";
// 引入对应的骨架屏页面
import homeSkeleton from "./views/homeSkeleton";
import detailSkeleton from "./views/detailSkeleton";
export default new Vue({
components: {
homeSkeleton,
detailSkeleton,
},
template: `
<div>
<homeSkeleton id="homeSkeleton" style="display:none;" />
<detailSkeleton id="detailSkeleton" style="display:none;" />
</div>
`,
});
以上这些就是SPA项目的优化手段,像其他优化的方式还有很多,但是我们应该在项目的开发阶段就考虑这样的方案,不然会导致后续维护和优化的成本变高。所以如有必要,我们应该尽量在早期的时候就开始干涉项目的优化进度和性能水平,这样才能持续输出有质量和易维护的产品。