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


前端開發社區的繁榮,造就了很多優秀的基於 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM