1. 程式人生 > >Vue.js 子組件的異步加載及其生命周期控制

Vue.js 子組件的異步加載及其生命周期控制

creat vue nor 其中 pmo dem con tor pan

前端開發社區的繁榮,造就了很多優秀的基於 MVVM 設計模式的框架,而組件化開發思想也越來越深入人心。這其中不得不提到 Vue.js 這個專註於 VM 層的框架。

本文主要對 Vue.js 組件化開發中子組件的異步加載和其生命周期進行一些探討。閱讀本文需要對 Vue.js 有一定的了解。

註意:本文中的一些例子代碼,是以 vue-cli 采用 webpack 模板初始化的項目為基礎。

異步組件

討論異步加載,需要先了解下異步組件。Vue.js 的異步組件是把組件定義為一個工廠函數,在組件需要渲染時觸發工廠函數,並且把結果緩存起來,用於後面的再次渲染。例如註冊一個全局異步組件:

Vue.component(‘async-demo‘, function(resolve, reject) {
  setTimeout(function() {
    // 將組件定義傳入 resolve 回調函數
    resolve({
      template: ‘<div>I am async!</div>‘
      // 組件的其他選項
    })
  }, 1000)
})

異步子組件和全局註冊很類似:

Vue.component(‘parent-demo‘, {
  // 父組件的其他選項
  components: {
    ‘async-demo‘: function(resolve, reject) {
      setTimeout(function() {
        // 將組件定義傳入 resolve 回調函數
        resolve({
          template: ‘<div>I am async!</div>‘
          // 子組件的其他選項
        })
      }, 1000)
    }
  }
})

工廠函數的第一個參數 resolve成功後的回調,第二個參數 reject失敗後的回調,可以在這裏提示用戶加載失敗等。

這裏使用 setTimeout 只是為了模擬異步,在實際項目中,應該配合 webpack 的代碼分離功能來實現異步加載。

異步加載

在實際的項目中,如果不使用異步加載,則 Vue.js 組件的 JS、CSS 和模板都會打包到一個 .js 文件中,這個文件可能達到幾 MB 甚至更多,嚴重影響首屏加載時間。所以在項目中我們需要啟用組件的異步加載。

webpack 代碼分離

webpack 的代碼分離有兩種,第一種,也是優先選擇的方式是,使用符合 ECMAScript 提案的 import()

語法。第二種,則是使用 webpack 特定的 require.ensure。讓我們先看看第一種:

import() 調用會在內部用到 promises。如果在舊有版本瀏覽器中使用 import(),記得使用一個 polyfill 庫(例如 es6-promise 或 promise-polyfill),來 shim Promise。
Vue.component(
  ‘async-demo‘,
  // 該 import 函數返回一個 Promise 對象。
  () => import(‘./async-demo‘)
)

上面的例子中,前文提到的工廠函數支持返回一個 Promise 對象,所以可以使用 import() 這種代碼分離方式。

局部註冊也是類似的:

Vue.component(‘parent-demo‘, {
  // 父組件的其他選項
  components: {
    ‘async-demo‘: () => import(‘./async-demo‘)
  }
})

本質上,import() 函數返回一個 Promise 實例,你可以自定義這個過程,下文會有說明。

第二種 webpack 代碼分離是這樣的:

Vue.component(‘async-demo‘, function(resolve) {
  require.ensure([], function(require) {
    resolve(require(‘./async-demo‘))
  }, function(error) {
    // 加載出錯執行這裏
  })
})

看起來比較繁瑣,如果你使用 webpack 2 及以上版本,則不建議使用第二種方式。

生命周期控制

在使用子組件(或者叫局部註冊)時,我們可能需要在子組件實例化(或者叫創建完畢)後做某些事情。在非異步的子組件中,我們很容易做這件事:

<template>
  <div>
    <my-demo ref="demo"></my-demo>
  </div>
</template>

<script>
import Demo from ‘./Demo‘

export default {
  mounted() {
    // 在這裏可以通過組件的 $refs 獲取到子組件的實例
    // 可以認為,在這裏子組件實例化完畢
    console.log(this.$refs.demo)
  },
  components: {
    MyDemo: Demo
  }
}
</script>

上例中使用了 Vue.js 的子組件引用,所以可以在生命周期函數 mounted 中很方便的獲取到子組件的實例,這樣就可以在這個函數中處理一些子組件實例化後要做的事情。

但是在異步子組件中,mounted 函數中是無法獲取到子組件的實例的,所以我們需要一些技巧來實現這個功能。

<template>
  <div>
    <my-demo ref="demo"></my-demo>
  </div>
</template>

<script>
export default {
  components: {
    MyDemo: () => import(‘./Demo‘).then(component => {
      // 清理已緩存的組件定義
      component.default._Ctor = {}

      if (!component.default.attached) {
        // 保存原組件中的 created 生命周期函數
        component.default.backupCreated = component.default.created
      }

      // 註入一個特殊的 created 生命周期函數
      component.default.created = function() {
        // 子組件已經實例化完畢

        // this 即為子組件 vm 實例
        console.log(this)

        if (component.default.backupCreated) {
          // 執行原組件中的 created 生命周期函數
          component.default.backupCreated.call(this)
        }
      }

      // 表示已經註入過了 
      component.default.attached = true

      return component
    })
  }
}
</script>

上例中,可以看到我們對組件異步加載做了一些特殊的控制,其中 import().then() 則是在加載完子組件的 .js 文件後,實例化子組件之前的回調,如果需要處理出錯的情況,則 import().then().catch() 即可。

以上代碼只是註入了一個 created 函數,如果要註入其他生命周期函數,例如 mounted,也是類似的:

<template>
  <div>
    <my-demo ref="demo"></my-demo>
  </div>
</template>

<script>
export default {
  components: {
    MyDemo: () => import(‘./Demo‘).then(component => {
      component.default._Ctor = {}

      if (!component.default.attached) {
        component.default.backupMounted = component.default.mounted
      }

      component.default.mounted = function() {
        if (component.default.backupMounted) {
          component.default.backupMounted.call(this)
        }
      }

      component.default.attached = true

      return component
    })
  }
}
</script>

通過上面的討論,我們可以做到完全控制 Vue.js 組件的異步加載的全過程,這對於需要精確控制子組件加載的組件,會有很大的幫助。

演示項目

根據上面的思路,寫了一個基於 Bootstrap 的異步彈窗演示項目:

https://github.com/hex-ci/vue-async-bootstrap-modal-demo

Vue.js 子組件的異步加載及其生命周期控制