Vue-cli 项目优化归纳(打包、源码、用户体验)
前言:vue-cli項目開發打包部署后,存在問題有首次首頁加載過慢,包括加載緩慢問題,需要進行vue項目優化。下面是對vue性能優化方法進行歸納,后面會對方法進行親測。
主要包括:代碼包打包優化、編碼優化、用戶體驗優化
?
一、代碼包打包優化
可以在谷歌瀏覽器的調試工具(F12)中看到打包后生成的app.js文件過大;
1、屏蔽sourceMap
進行打包源碼上線環節,需要對項目開發環節的開發提示信息以及錯誤信息進行屏蔽,一方面可以減少上線代碼包的大小;另一方面提高系統的安全性。在vuejs項目的config目錄下有三個文件dev.env.js(開發環境配置文件)、prod.env.js(上線配置文件)、index.js(通用配置文件)。vue-cli腳手架在上線配置文件會自動設置允許sourceMap打包,所以在上線前可以屏蔽sourceMap。如下所示,index.js的配置如下,通用配置文件分別對開發環境和上線環境做了打包配置分類,在build對象中的配置信息中,productionSourceMap修改成false:
module.exports = {dev: {...},build: {// Template for index.htmlindex: path.resolve(__dirname, '../dist/ndindex.html'),// PathsassetsRoot: path.resolve(__dirname, '../dist'),assetsSubDirectory: 'static',assetsPublicPath: './',/*** Source Maps*/productionSourceMap: false, //這里關閉Source Maps// https://webpack.js.org/configuration/devtool/#productiondevtool: '#source-map',// Gzip off by default as many popular static hosts such as// Surge or Netlify already gzip all static assets for you.// Before setting to `true`, make sure to:// npm install --save-dev compression-webpack-pluginproductionGzip: true,productionGzipExtensions: ['js', 'css','svg'],// Run the build command with an extra argument to// View the bundle analyzer report after build finishes:// `npm run build --report`// Set to `true` or `false` to always turn it on or offbundleAnalyzerReport: process.env.npm_config_report} }
2、對項目代碼中的JS/CSS/SVG(*.ico)文件進行gzip壓縮
在vue-cli腳手架的配置信息中,有對代碼進行壓縮的配置項,例如index.js的通用配置,productionGzip設置為true,但是首先需要對compress-webpack-plugin支持,所以需要通過 npm install --save-dev compression-webpack-plugin,gzip會對js、css文件進行壓縮處理;對于圖片進行壓縮問題,對于png,jpg,jpeg沒有壓縮效果,對于svg,ico文件以及bmp文件壓縮效果達到50%,在productionGzipExtensions: ['js', 'css','svg']設置需要進行壓縮的什么格式的文件。對項目文件進行壓縮之后,需要瀏覽器客戶端支持gzip以及后端支持gzip。下面可以查看成功支持gzip狀態:
module.exports = {dev: {...},build: {...// Gzip off by default as many popular static hosts such as// Surge or Netlify already gzip all static assets for you.// Before setting to `true`, make sure to:// npm install --save-dev compression-webpack-pluginproductionGzip: true,productionGzipExtensions: ['js', 'css','svg'],...} }?
?
二、源碼優化
1、對路由組件進行懶加載
?懶加載也叫延遲加載,即在需要的時候進行加載,隨用隨載。
在路由配置文件里,這里是router.js里面引用組件。如果使用同步的方式加載組件,在首屏加載時會對網絡資源加載加載比較多,資源比較大,加載速度比較慢。所以設置路由懶加載,按需加載會加速首屏渲染。在沒有對路由進行懶加載時,在Chrome里devtool查閱可以看到首屏網絡資源加載情況(6requests 3.8MB transfferred Finish:4.67s DOMContentLoaded 2.61s Load 2.70s)。在對路由進行懶加載之后(7requests 800kb transffered Finish2.67s DOMContentLoaded 1.72s Load 800ms),可以看見加載速度明顯加快。但是進行懶加載之后,實現按需加載,那么項目打包不會把所有js打包進app.[hash].js里面,優點是可以減少app.[hash].js體積,缺點就是會把其它js分開打包,造成多個js文件,會有多次https請求。如果項目比較大,需要注意懶加載的效果
使用如下:
routes: [{ path: "/", redirect: "index" },{path: "/",name: "home",component: resolve=>require(["@/views/home"],resolve),children: [{// 員工查詢path: "/employees",component: resolve=>require(["@/components/employees"],resolve)},{// 首頁path: "/index",component: resolve=>require(["@/views/index"],rolve)},]} ]?
?
2、組件異步加載
vue官網指南,提到異步組件的使用,在大型應用中,我們可能需要將應用分割成小一些的代碼塊,并且只在需要的時候才從服務器加載一個模塊。Vue 只有在這個組件需要被渲染的時候才會觸發工廠函數,且會把結果緩存起來供未來重渲染。
2.1、v-if惰性結合setTimeout 使組件異步加載
加載首頁的時候,可以先給首頁的子組件設置v-if = “false”,在頁面初始化的時候再給子組件設置為true,此方法利用了v-if的惰性,setTimeout會使子組件在所有的組件初始化完成并顯示后再對其子組件進行初始化。
注:在實際開發中還遇到了另一種情況也可以用此方法解決,在入口js中獲取了app的token,但是在具體頁面中發現不管是在created還是mounted中都是有時候能獲取到token,有時候又不可以,是因為執行順序的原因,可以通過 setTimeout 時間設置為0 這種方法把用到token的請求方法給排到最后,這樣就能保證請求方法中有token了。
?
? ? 2.2、異步組件,按需加載
webpack 2 結合 ES2015 語法如下。
Vue.component('async-webpack-example',// 這個 `import` 函數會返回一個 `Promise` 對象。() => import('./my-async-component') )上面全局組件,當使用局部注冊的時候如下,因為import函數返回的是一個promise對象,因此可以用promise本身的then()和catch()方法去監聽到組件的加載。
new Vue({// ...components: {'my-component': () => import('./my-async-component')} })?
?
3、vue-lazyload插件,圖片懶加載
項目中過多的圖片會嚴重影響網頁的加載速度,并且移動網絡下的流量消耗巨大,所以說延遲加載幾乎是標配了。?? 圖片懶加載,顯示當前用戶界面再加載顯示圖片,實現的原理很簡單,就是我們先設置圖片的data-set屬性(當然也可以是其他任意的,只要不會發送http請求就行了,作用就是為了存取值)值為其圖片路徑,由于不是src,所以不會發送http請求。 然后我們計算出頁面scrollTop的高度和瀏覽器的高度之和, 如果圖片舉例頁面頂端的坐標Y(相對于整個頁面,而不是瀏覽器窗口)小于前兩者之和,就說明圖片就要顯示出來了(合適的時機,當然也可以是其他情況),這時候我們再將 data-set 屬性替換為 src 屬性即可。
3.1、JavaScript實現:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Lazyload 2</title><style>img {display: block;margin-bottom: 50px;height: 200px;}</style> </head> <body><img src="images/loading.gif" data-src="images/1.png"><img src="images/loading.gif" data-src="images/2.png"><img src="images/loading.gif" data-src="images/3.png"><img src="images/loading.gif" data-src="images/4.png"><img src="images/loading.gif" data-src="images/5.png"><img src="images/loading.gif" data-src="images/6.png"><script>function throttle(fn, delay, atleast) {//函數綁定在 scroll 事件上,當頁面滾動時,避免函數被高頻觸發,var timeout = null,//進行去抖處理startTime = new Date();return function() {var curTime = new Date();clearTimeout(timeout);if(curTime - startTime >= atleast) {fn();startTime = curTime;}else {timeout = setTimeout(fn, delay);}}}function lazyload() {var images = document.getElementsByTagName('img');var len = images.length;var n = 0; //存儲圖片加載到的位置,避免每次都從第一張圖片開始遍歷 return function() {var seeHeight = document.documentElement.clientHeight;var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;for(var i = n; i < len; i++) {if(images[i].offsetTop < seeHeight + scrollTop) {if(images[i].getAttribute('src') === 'images/loading.gif') {images[i].src = images[i].getAttribute('data-src');}n = n + 1;}}}}var loadImages = lazyload();loadImages(); //初始化首頁的頁面圖片window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);//函數節流(throttle)與函數去抖(debounce)處理, //500ms 的延遲,和 1000ms 的間隔,當超過 1000ms 未觸發該函數,則立即執行該函數,不然則延遲 500ms 執行該函數</script> </body> </html>?
3.2、vue-cli項目中vue-lazyload 插件實現
3.2.1. 安裝插件:
npm install vue-lazyload --save-dev3.2.2. main.js引入插件:
import VueLazyLoad from 'vue-lazyload' Vue.use(VueLazyLoad,{error:'./static/error.png',loading:'./static/loading.png' })3.2.3. vue文件中將需要懶加載的圖片綁定 v-bind:src 修改為 v-lazy?
<img class="item-pic" v-lazy="newItem.picUrl"/>?功能擴展:
圖片懶加載的簡單效果已經實現了,然后就可以按這開發文檔的api進行擴展了:
| preLoad | proportion of pre-loading height(預加載高度比例) | 1.3 | Number |
| error | src of the image upon load fail(圖片路徑錯誤時加載圖片) | 'data-src' | String |
| loading | src of the image while loading(預加載圖片) | 'data-src' | String |
| attempt | attempts count(嘗試加載圖片數量) | 3 | Number |
| listenEvents | events that you want vue listen for (想要監聽的vue事件) 默認['scroll']可以省略, 當插件跟頁面中的動畫或過渡等事件有沖突是, 可以嘗試其他選項 | ['scroll'(默認), 'wheel', 'mousewheel', 'resize', 'animationend', 'transitionend', 'touchmove'] | Desired Listen Events |
| adapter | dynamically modify the attribute of element (動態修改元素屬性) | { } | Element Adapter |
| filter | the image's listener filter(動態修改圖片地址路徑) | { } | Image listener filter |
| lazyComponent | lazyload component | false | Lazy Component |
| dispatchEvent | trigger the dom event | false | Boolean |
| throttleWait | throttle wait | 200 | Number |
| observer | use IntersectionObserver | false | Boolean |
| observerOptions | IntersectionObserver options | { rootMargin: '0px', threshold: 0.1 } | IntersectionObserver |
?
?
4、引入外部插件或CDN引用,不要在vue中引入
我們可以打包? 時不打包 vue、vuex、vue-router、axios 等,換用國內的?bootcdn?直接引入到根目錄的 index.html 中,這樣可以減少app.js大小。采用CDN外部加載,去掉其他頁面的組件import,修改webpack.base.config.js,在externals中加入該組件,這是為了避免編譯時找不到組件報錯。
<script src="//cdn.bootcss.com/vue/2.2.5/vue.min.js"></script> <script src="//cdn.bootcss.com/vue-router/2.3.0/vue-router.min.js"></script> <script src="//cdn.bootcss.com/vuex/2.2.1/vuex.min.js"></script> <script src="//cdn.bootcss.com/axios/0.15.3/axios.min.js"></script> externals: {'vue': 'Vue','vue-router': 'VueRouter','vuex': 'Vuex','axios': 'axios' }?
?
5、使用到第三方庫的時按需引用
在項目開發中,我們會用到很多第三方庫,如果可以按需引入,我們可以只引入自己需要的組件,來減少組件庫所占空間,如element-ui組件庫按需只加載部分組件Button、Select,官網?按需引入element-ui
5.1.????安裝babel-plugin-component插件:
npm install babel-plugin-component -D5.2.????配置插件,將?.babelrc修改為:
{"presets": [["es2015", { "modules": false }]],"plugins": [["component",{"libraryName": "element-ui","styleLibraryName": "theme-chalk"}]] }5.3.????引入部分組件,比如 Button 和 Select,那么需要在 main.js 中寫入以下內容:
import Vue from 'vue'; import { Button, Select } from 'element-ui'; import App from './App.vue';Vue.component(Button.name, Button); Vue.component(Select.name, Select); /* 或寫為* Vue.use(Button)* Vue.use(Select)*/new Vue({el: '#app',render: h => h(App) });?
?
三、用戶體驗優化
1、loading加載效果
用于加載數據時顯示動效。當請求服務端接口需要一定時間時,在請求時加上一個loading 加載動畫效果將極大提升用戶體驗和減輕服務端壓力。
?
實現方案:
a、使用elementUI的loading組件,可以通過指令或服務的形式調用。
指令形式調用:可以自定義加載動畫的文字、遮罩層顏色、spinner加載圖標的類名,如下:
<template><el-tablev-loading="loading"element-loading-text="拼命加載中"element-loading-spinner="el-icon-loading"element-loading-background="rgba(0, 0, 0, 0.8)":data="tableData"style="width: 100%"><el-table-columnprop="date"label="日期"width="180"></el-table-column><el-table-columnprop="name"label="姓名"width="180"></el-table-column><el-table-columnprop="address"label="地址"></el-table-column></el-table> </template><script>export default {data() {return {tableData: [{date: '2016-05-03',name: '王小虎',address: '上海市普陀區金沙江路 1518 弄'}, {date: '2016-05-02',name: '王小虎',address: '上海市普陀區金沙江路 1518 弄'}, {date: '2016-05-04',name: '王小虎',address: '上海市普陀區金沙江路 1518 弄'}],loading: true};}}; </script>上述loading布爾值可以結合vuex狀態進行全局控制是否展示loading效果;
?
?
?
引入 Loading 服務:
import { Loading } from 'element-ui';在需要調用時:
Loading.service(options);其中?options?參數為 Loading 的配置項,具體見下表。LoadingService?會返回一個 Loading 實例,可通過調用該實例的?close?方法來關閉它:
let loadingInstance = Loading.service(options); this.$nextTick(() => { // 以服務的方式調用的 Loading 需要異步關閉loadingInstance.close(); });需要注意的是,以服務的方式調用的全屏 Loading 是單例的:若在前一個全屏 Loading 關閉前再次調用全屏 Loading,并不會創建一個新的 Loading 實例,而是返回現有全屏 Loading 的實例:
let loadingInstance1 = Loading.service({ fullscreen: true }); let loadingInstance2 = Loading.service({ fullscreen: true }); console.log(loadingInstance1 === loadingInstance2); // true此時調用它們中任意一個的?close?方法都能關閉這個全屏 Loading。
如果完整引入了 Element,那么 Vue.prototype 上會有一個全局方法?$loading,它的調用方式為:this.$loading(options),同樣會返回一個 Loading 實例
?
?
?
b、可以使用命令【npm install --save vue-element-loading】安裝該插件后直接使用
使用:
import Vue from 'vue'import VueElementLoading from 'vue-element-loading'Vue.component('VueElementLoading', ElementLoading)Or
import VueElementLoading from 'vue-element-loading'export default {components: {VueElementLoading}}?
//全屏 <vue-element-loading :active="isActive" :is-full-screen="true"/>//組件內容器 <div class="my-container"><vue-element-loading :active="isActive" spinner="bar-fade-scale" color="#FF6700"/><span>Lorem ipsum dolor sit amet, consectetur adipiscing elit.Fusce id fermentum quam. Proin sagittis, nibh id hendrerit imperdiet, elit sapien laoreet elit.</span> </div>Options
| active | Boolean | - | Status for show/hide loading |
| spinner | String | spinner | Spinner icon name: spinner, mini-spinner, ring, line-wave, line-scale, line-down, bar-fade, bar-fade-scale |
| color | String | #ccc | Color of spinner icon |
| is-full-screen | Boolean | false | Loader will overlay the full page |
| background-color | String | rgba(255, 255, 255, .9) | Background color of spinner icon (for overlay) |
| size | String | 40 | The size to display the spinner in pixels (NOTE: this will not affect custom spinner images) |
| duration | String | 0.6 | The duration of one 'loop' of the spinner animation, in seconds (NOTE: this will not affect custom spinner images) |
| text | String | - | Text will appear below loader |
| text-style | Object | {} | Change style of the text below loader |
?
使用參考網址:https://biigpongsatorn.github.io/#/vue-element-loading可以看到有不同的loading動畫效果,如下圖
?
?
?
2、骨架屏加載
背景:使用Vue和Webpack進行**MPA(單、多頁面應用)**的開發,一般會在頁面進行數據接口等待時增加Loading動畫,為用戶提供較好的交互體驗。但是會發現,Loading展示的時機是在Vue框架解析后,也就是說需要如下幾個條件才能顯示:
- HTML文件加載完成
- JavaScript文件加載完成
- window對象中完成webpackJsonp方法的添加
因此:等待的時間=HTML加載時間+JS加載時間+JS全局環境創建的執行時間。若是JS資源文件較大,或者存在過多的圖片資源,導致資源速度下載較慢時,用戶所能看到的白屏時間便較長。
因此添加骨架屏,其優勢在于:
- 寫于HTML文件中,獨立于Vue框架,節省了JS加載時間+JS全局環境創建的執行時間的時間
- 只在主頁面根據頁面結構獨立編寫,預先展示頁面結構,進行視覺暫留,提供更好的交互感官
- 只在頁面結構變化時進行修改,維護成本相對較低
骨架屏的作用主要是在網絡請求較慢時,提供基礎占位,當數據加載完成,恢復數據展示。這樣給用戶一種很自然的過渡,不會造成頁面長時間白屏或者閃爍等情況。 常見的骨架屏實現方案有ssr服務端渲染和prerender兩種解決方案。
?
詳細使用見本人文章:(親測)vue-cli項目添加骨架屏多種方式,自動生成骨架屏
?
?
?
?
PS:
一些開發經驗或習慣可以參考文章:https://blog.csdn.net/crazywoniu/article/details/73480344
可以學習的有:
- v-show,v-if 選擇哪個??v-if,因為減少了 dom 數量,加快首屏渲染,v-if是懶加載,當狀態為true時才會加載,并且為false時不會占用布局空間;v-show是無論狀態是true或者是false,都會進行渲染,并對布局占據空間對于在項目中,需要頻繁調用,不需要權限的顯示隱藏,可以選擇使用v-show,可以減少系統的切換開銷。。
- 盡量不在模板里面寫過多的表達式與判斷?v-if="isShow && isAdmin && (a || b)",這種表達式雖說可以識別,但是不是長久之計,當看著不舒服時,適當的寫到 methods 和 computed 里面封裝成一個方法,這樣的好處是方便我們在多處判斷相同的表達式,其他權限相同的元素再判斷展示的時候調用同一個方法即可。
- 循環調用子組件時添加 key,如?(item, index) in arr,然后?:key="index"來確保 key 的唯一性。在列表數據進行遍歷渲染時,需要為每一項item設置唯一key值,方便vuejs內部機制精準找到該條列表數據。當state更新時,新的狀態值和舊的狀態值對比,較快地定位到diff。
- 組件內樣式命名盡量采用簡短的命名規則,不需要?.header-title__text?之類的 class,直接?.title?搞定。
- 全局的樣式文件,盡量抽象化,既然不在每一個組件里重復寫,就盡量通用,這部分抽象做的越好說明你的樣式文件體積越小,復用率越高。建議將復寫組件庫如 Element 樣式的代碼也放到全局中去。
- 盡量不使用 float 布局,之前看到很多人封裝了?.fl -- float: left?到全局文件里去,然后又要?.clear,現在的瀏覽器還不至于弱到非要用?float?去兼容,完全可以 flex,grid 兼容性一般,功能其實 flex 布局都可以實現,float 會帶來布局上的麻煩,用過的都知道不相信解釋坑了。
- 盡量保持每個組件?export default {}?內的方法順序一致,方便查找對應的方法。我個人習慣 data、props、鉤子、watch、computed、components。
- props 父子組件傳值時盡量?:width="" :heigth=""?不要?:option={},細化的好處是只傳需要修改的參數,在子組件 props 里加數據類型,是否必傳,以及默認值,便于排查錯誤,讓傳值更嚴謹
- watch 和 computed 用哪個?,計算屬性主要是做一層 filter 轉換,切忌加一些調用方法進去,watch 的作用就是監聽數據變化去改變數據或觸發事件如?this.$store.dispatch('update', { ... })
- computed 中不能依賴一個計算結果去計算另一個計算值,因為computed中計算值是異步的,可能會報錯undefined;。當watch的數據比較小,性能消耗不明顯。當數據變大,系統會出現卡頓,所以減少watch的數據。
- 組件分類,我習慣性的按照三類劃分,page、page-item 和 layout,page 是路由控制的部分,page-item 屬于 page 里各個布局塊如 banner、side 等等,layout 里放置多個頁面至少出現兩次的組件,如 icon, scrollTop 等,組件盡量實現 "高內聚低耦合";
- vuex狀態過大時可以使用官網提供的模塊化方案,vuex使用建議:盡量跑完完整的閉環是 store.dispatch('action') -> action -> commit -> mutation -> getter -> computed,為方便后期管理,在我的組件里只出現 dispatch 和 mapGetters,其余的流程都在名為 store 的 vuex 文件夾里進行。
- SSR(服務端渲染):如果項目比較大,首屏無論怎么做優化,都出現閃屏或者一陣黑屏的情況。可以考慮使用SSR(服務端渲染),vuejs官方文檔提供next.js很好的服務端解決方案,但是局限性就是目前僅支持Koa、express等Nodejs的后臺框架,需要webpack支持。目前自己了解的就是后端支持方面,vuejs的后端渲染支持php,其它的不太清楚。
?
?
?
?
參考文章:
淺談 Vue 項目優化:https://blog.csdn.net/crazywoniu/article/details/73480344
vuejs項目性能優化總結:https://www.jianshu.com/p/41075f1f5297
關于vue在app首次加載緩慢的解決辦法:https://www.jianshu.com/p/6262772bdc9c
圖片懶加載和預加載:https://www.cnblogs.com/rlann/p/7296660.html
vue-lazyload 使用:https://www.cnblogs.com/xyyt/p/7650539.html
vue骨架屏官網:https://github.com/lavas-project/vue-skeleton-webpack-plugin
vue-element-loading:?https://biigpongsatorn.github.io/#/vue-element-loading
?
總結
以上是生活随笔為你收集整理的Vue-cli 项目优化归纳(打包、源码、用户体验)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【机器学习】模型又线上线下不一致怎么办?
- 下一篇: 【深度学习】19家机构联合发布,200页