vue專案優化

2020-10-17 12:01:02

一、程式碼優化

1. 使用keep-alive快取不活動的元件

keep-alive是一個抽象元件:它自身不會渲染一個DOM元素,也不會出現在父元件鏈中;使用keep-alive包裹動態元件時,會快取不活動的元件範例,而不是銷燬它們。

  • 在動態元件中的應用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
     <component :is="currentComponent"></component>
</keep-alive>
  • 在vue-router中的應用
<keep-alive :include="whiteList" :exclude="blackList">
    <router-view></router-view>
</keep-alive>

include定義快取白名單,keep-alive會快取命中的元件;exclude定義快取黑名單,被命中的元件將不會被快取;
很多時候也可以配合路由的meta屬性使用

export default[
 {
  path:'/',
  name:'home',
  components:Home,
  meta:{
    keepAlive:true //需要被快取的元件
 },
 {
  path:'/book',
  name:'book',
  components:Book,
  meta:{
     keepAlive:false //不需要被快取的元件
 } 
]
<keep-alive>
  <router-view v-if="this.$route.meat.keepAlive"></router-view>
  <!--這裡是會被快取的元件-->
</keep-alive>
<keep-alive v-if="!this.$router.meta.keepAlive"></keep-alive>
<!--這裡是不會被快取的元件-->

2. 使用路由懶載入

Vue 是單頁面應用,可能會有很多的路由引入 ,這樣使用 webpcak 打包後的檔案很大,當進入首頁時,載入的資源過多,頁面會出現白屏的情況,不利於使用者體驗。如果我們能把不同路由對應的元件分割成不同的程式碼塊,然後當路由被存取的時候才載入對應的元件,這樣就更加高效了。這樣會大大提高首屏顯示的速度,但是可能其他的頁面的速度就會降下來。

路由懶載入:

export default new Router({
  mode: 'history',
  routes: [
			{
		     path: '/',
		     component: ()=>import('@/components/DefaultIndex') }
		     ]
		     })

詳見另一篇部落格:前端優化中的路由懶載入

3. 圖片懶載入

對於圖片過多的頁面,為了加速頁面載入速度,所以很多時候我們需要將頁面內未出現在可視區域內的圖片先不做載入, 等到捲動到可視區域後再去載入。這樣對於頁面載入效能上會有很大的提升,也提高了使用者體驗。我們在專案中使用 Vue 的 vue-lazyload 外掛:

npm引入:npm i vue-lazyload -S
CDN引入:[https://unpkg.com/vue-lazyload/vue-lazyload.js](https://unpkg.com/vue-lazyload/vue-lazyload.js)

使用:
main.js:

import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
 
Vue.use(VueLazyload)
 
// or with options
Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: 'dist/error.png',
  loading: 'dist/loading.gif',
  attempt: 1
})
 
new Vue({
  el: 'body',
  components: {
    App
  }
})

template:

<ul>
  <li v-for="img in list">
    <img v-lazy="img.src" >
  </li>
</ul>

4. 使用節流防抖函數(效能優化)

那麼在 vue 中怎麼使用呢:
在公共方法中(如 untils.js 中),加入函數防抖和節流方法

// 防抖
export function _debounce(fn, delay) {
    var delay = delay || 200;
    var timer;
    return function () {
        var th = this;
        var args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(function () {
            timer = null;
            fn.apply(th, args);
        }, delay);
    };
}
// 節流
export function _throttle(fn, interval) {
    var last;
    var timer;
    var interval = interval || 200;
    return function () {
        var th = this;
        var args = arguments;
        var now = +new Date();
        if (last && now - last < interval) {
            clearTimeout(timer);
            timer = setTimeout(function () {
                last = now;
                fn.apply(th, args);
            }, interval);
        } else {
            last = now;
            fn.apply(th, args);
        }
    }
}

在需要使用的元件參照

import { _debounce } from "@/utils/public";

在 methods 中使用

  methods: {
    // 改變場數
    changefield: _debounce(function(_type, index, item) {
        // do something ...
    }, 200)
  }

應用:

函數防抖(debounce)

在事件被觸發n秒後再執行回撥,如果在這n秒內又被觸發,則重新計時。

<body>
      <input type="text" id='unDebounce'>
</body>
</html>

<script>

    //模擬一段ajax請求
    function ajax(content){ 
        console.log('ajax request ' + content) 
        };
    letinputa = document.getElementById('unDebounce');
        function fn(e){ ajax(e.target.value) }
    //防抖函數,處理多次被觸發的事件,只執行最後一次
    inputa.addEventListener('input', fn)
</script>

看一下執行結果:
在這裡插入圖片描述
可以看到,我們只要輸入一個字元,就會觸發這次ajax請求。不僅從資源上來說是很浪費的行為,而且實際應用中,使用者也是輸出完整的字元后,才會請求。下面我們優化一下:

<body>
      <input type="text" id='unDebounce'>
</body>
</html>

<script>
    //防抖函數
    function _debounce(fn, delay) {
    var delay = delay || 200;
    var timer;
    return function () {
        var th = this;
        var args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(function () {
            timer = null;
            fn.apply(th, args);
        }, delay);
    };
}

    //模擬一段ajax請求
    function ajax(content){ 
        console.log('ajax request ' + content) 
        };
    let inputa = document.getElementById('unDebounce');
    function fn(e){ ajax(e.target.value) }
    //防抖函數,處理多次被觸發的事件,只執行最後一次
    inputa.addEventListener('input', _debounce(fn,1000))
</script>

在這裡插入圖片描述
我們加入了防抖以後,當你在頻繁的輸入時,並不會傳送請求,只有當你在指定間隔內沒有輸入時,才會執行函數。如果停止輸入但是在指定間隔內又輸入,會重新觸發計時。

個人理解 函數防抖就是法師發技能的時候要讀條,技能讀條沒完再按技能就會重新讀條。

函數節流(throttle)

規定在一個單位時間內,只能觸發一次函數。如果這個單位時間內觸發多次函數,只有一次生效。


<body>
      <input type="text" id='unDebounce'>
</body>
</html>

<script>
//節流函數
function _throttle(fn, interval) {
    var last;
    var timer;
    var interval = interval || 200;
    return function () {
        var th = this;
        var args = arguments;
        var now = +new Date();
        if (last && now - last < interval) {
            clearTimeout(timer);
            timer = setTimeout(function () {
                last = now;
                fn.apply(th, args);
            }, interval);
        } else {
            last = now;
            fn.apply(th, args);
        }
    }
}
    //模擬一段ajax請求
    function ajax(content){ 
        console.log('ajax request ' + content) 
        };
    let inputa = document.getElementById('unDebounce');
    function fn(e){ ajax(e.target.value) }
    //防抖節流,無論你輸入多塊,每隔1秒鐘執行一次
    inputa.addEventListener('input', _throttle(fn,1000))
</script>

在這裡插入圖片描述

不管我們設定的執行時間間隔多小,總是1s內只執行一次。

個人理解 函數節流就是fps遊戲的射速,就算一直按著滑鼠射擊,也只會在規定射速內射出子彈。

總結

函數防抖和函數節流都是防止某一時間頻繁觸發,但是這兩兄弟之間的原理卻不一樣。
函數防抖是某一段時間內只執行一次,而函數節流是間隔時間執行。

結合應用場景

debounce

  • search搜尋聯想,使用者在不斷輸入值時,用防抖來節約請求資源。
  • window觸發resize的時候,不斷的調整瀏覽器視窗大小會不斷的觸發這個事件,用防抖來讓其只觸發一次

throttle

  • 滑鼠不斷點選觸發,mousedown(單位時間內只觸發一次)
  • 拖拽事件,每拖動1px都會觸發onmousemove(可以用throttle優化,每秒觸發一次)
  • 監聽捲動事件,比如是否滑到底部自動載入更多,用throttle來判斷

5. v-for 遍歷必須為 item 新增 key,且避免同時使用 v-if

  • v-for 遍歷必須為 item 新增 key

在列表資料進行遍歷渲染時,需要為每一項 item 設定唯一 key 值,方便 Vue.js 內部機制精準找到該條列表資料。當 state
更新時,新的狀態值和舊的狀態值對比,較快地定位到 diff 。

  • v-for 遍歷避免同時使用 v-if

v-for 比 v-if 優先順序高,如果每一次都需要遍歷整個陣列,將會影響速度,尤其是當之需要渲染很小一部分的時候,必要情況下應該替換成
computed 屬性。

推薦:

<template>
  <div class="home">
    <ul>
      <li
        v-for="user in activeUsers"
        :key="user.id">
        {{ user.name }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return {
      users:[{id:1,name:'zhangsan',isActive:true},{id:2,name:'lisi',isActive:true},{id:3,name:'wangwu',isActive:false},{id:4,name:'maliu',isActive:true},]
    }
  },
  computed: {
    activeUsers: function () {
    //  [js 的filter()方法](https://www.cnblogs.com/qiu2841/p/8961017.html)
      return this.users.filter(function (user) {
          return user.isActive
      })
    }
  }
}
</script>

不推薦:

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id">
    {{ user.name }}
  </li>
</ul>

6. v-if 和 v-show 區分使用場景

v-if 是 真正 的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子元件適當地被銷燬和重建;也是惰性的:如果在初始渲染時條件為假,則什麼也不做——直到條件第一次變為真時,才會開始渲染條件塊。

v-show就簡單得多, 不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於 CSS 的 display 屬性進行切換。

所以,v-if 適用於在執行時很少改變條件,不需要頻繁切換條件的場景; v-show則適用於需要非常頻繁切換條件的場景。

7. computed 和 watch 區分使用場景

computed: 是計算屬性,依賴其它屬性值,並且 computed 的值有快取,只有它依賴的屬性值發生改變,下一次獲取 computed 的值時才會重新計算 computed 的值;

watch: 更多的是「觀察」的作用,類似於某些資料的監聽回撥 ,每當監聽的資料變化時都會執行回撥進行後續操作;

運用場景:

  • 當我們需要進行數值計算,並且依賴於其它資料時,應該使用 computed,因為可以利用 computed 的快取特性,避免每次獲取值時,都要重新計算;

  • 當我們需要在資料變化時執行非同步或開銷較大的操作時,應該使用 watch,使用 watch 選項允許我們執行非同步操作 ( 存取一個 API ),限制我們執行該操作的頻率,並在我們得到最終結果前,設定中間狀態。這些都是計算屬性無法做到的。

8. 長列表效能優化

Vue 會通過 Object.defineProperty 對資料進行劫持,來實現檢視響應資料的變化,然而有些時候我們的元件就是純粹的資料展示,不會有任何改變,我們就不需要 Vue 來劫持我們的資料,在大量資料展示的情況下,這能夠很明顯的減少元件初始化的時間,那如何禁止 Vue 劫持我們的資料呢?可以通過 Object.freeze 方法來凍結一個物件,一旦被凍結的物件就再也不能被修改了。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

9. 事件的銷燬

Vue 元件銷燬時,會自動清理它與其它範例的連線,解綁它的全部指令及事件監聽器,但是僅限於元件本身的事件。 如果在 js 內使用 addEventListene 等方式是不會自動銷燬的,我們需要在元件銷燬時手動移除這些事件的監聽,以免造成記憶體洩露,如:

created() {
  addEventListener('click', this.click, false)
},
beforeDestroy() {
  removeEventListener('click', this.click, false)
}

10. 第三方外掛的按需引入

我們在專案中經常會需要引入第三方外掛,如果我們直接引入整個外掛,會導致專案的體積太大,我們可以藉助 babel-plugin-component ,然後可以只引入需要的元件,以達到減小專案體積的目的。以下為專案中引入 element-ui 元件庫為例:

(1)首先,安裝 babel-plugin-component :

 npm install babel-plugin-component -D
``2)然後,將 .babelrc 修改為:
```js
 {
   "presets": [["es2015", { "modules": false }]],
   "plugins": [
     [
       "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
     ]
   ]
 }

(3)在 main.js 中引入部分元件:

 import Vue from 'vue';
 import { Button, Select } from 'element-ui';

 Vue.use(Button)
 Vue.use(Select)

二、webpack打包優化

詳見我上一篇部落格vue-cli4打包優化(webapck優化)

最後別忘記 「點贊」