來源:Hex 發(fā)布時間:2018-07-03 14:28:27 閱讀量:1679
討論異步加載,需要先了解下異步組件。Vue.js 的異步組件是把組件定義為一個工廠函數(shù),在組件需要渲染時觸發(fā)工廠函數(shù),并且把結(jié)果緩存起來,用于后面的再次渲染。例如注冊一個全局異步組件:
Vue.component('async-demo', function(resolve, reject) {setTimeout(function() {// 將組件定義傳入 resolve 回調(diào)函數(shù)resolve({template: '<div>I am async!</div>' // 組件的其他選項(xiàng)})}, 1000)})
異步子組件和全局注冊很類似:
Vue.component('parent-demo', { // 父組件的其他選項(xiàng) components: { 'async-demo': function(resolve, reject) {setTimeout(function() {// 將組件定義傳入 resolve 回調(diào)函數(shù)resolve({template: '<div>I am async!</div>' // 子組件的其他選項(xiàng)})}, 1000)}}})
工廠函數(shù)的第一個參數(shù) resolve
為成功后的回調(diào),第二個參數(shù) reject
為失敗后的回調(diào),可以在這里提示用戶加載失敗等。
這里使用 setTimeout
只是為了模擬異步,在實(shí)際項(xiàng)目中,應(yīng)該配合 webpack 的代碼分離功能來實(shí)現(xiàn)異步加載。
在實(shí)際的項(xiàng)目中,如果不使用異步加載,則 Vue.js 組件的 JS、CSS 和模板都會打包到一個 .js 文件中,這個文件可能達(dá)到幾 MB 甚至更多,嚴(yán)重影響首屏加載時間。所以在項(xiàng)目中我們需要啟用組件的異步加載。
webpack 的代碼分離有兩種,第一種,也是優(yōu)先選擇的方式是,使用符合 ECMAScript 提案的 import()
語法。第二種,則是使用 webpack 特定的 require.ensure
。讓我們先看看第一種:
import() 調(diào)用會在內(nèi)部用到 promises。如果在舊有版本瀏覽器中使用 import(),記得使用一個 polyfill 庫(例如 es6-promise 或 promise-polyfill),來 shim Promise。
Vue.component('async-demo',// 該 import 函數(shù)返回一個 Promise 對象。() => import('./async-demo'))
上面的例子中,前文提到的工廠函數(shù)支持返回一個 Promise 對象,所以可以使用 import()
這種代碼分離方式。
局部注冊也是類似的:
Vue.component('parent-demo', { // 父組件的其他選項(xiàng) components: { 'async-demo': () => import('./async-demo')}})
本質(zhì)上,import()
函數(shù)返回一個 Promise 實(shí)例,你可以自定義這個過程,下文會有說明。
第二種 webpack 代碼分離是這樣的:
Vue.component('async-demo', function(resolve) {require.ensure([], function(require) { resolve(require('./async-demo'))}, function(error) { // 加載出錯執(zhí)行這里 })})
看起來比較繁瑣,如果你使用 webpack 2 及以上版本,則不建議使用第二種方式。
在使用子組件(或者叫局部注冊)時,我們可能需要在子組件實(shí)例化(或者叫創(chuàng)建完畢)后做某些事情。在非異步的子組件中,我們很容易做這件事:
<template><div><my-demo ref="demo"></my-demo></div></template><script>import Demo from './Demo'export default {mounted() { // 在這里可以通過組件的 $refs 獲取到子組件的實(shí)例 // 可以認(rèn)為,在這里子組件實(shí)例化完畢 console.log(this.$refs.demo)},components: {MyDemo: Demo}}</script>
上例中使用了 Vue.js 的子組件引用,所以可以在生命周期函數(shù) mounted
中很方便的獲取到子組件的實(shí)例,這樣就可以在這個函數(shù)中處理一些子組件實(shí)例化后要做的事情。
但是在異步子組件中,mounted
函數(shù)中是無法獲取到子組件的實(shí)例的,所以我們需要一些技巧來實(shí)現(xiàn)這個功能。
<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 生命周期函數(shù) component.default.backupCreated = component.default.created} // 注入一個特殊的 created 生命周期函數(shù) component.default.created = function() {// 子組件已經(jīng)實(shí)例化完畢 // this 即為子組件 vm 實(shí)例 console.log(this)if (component.default.backupCreated) { // 執(zhí)行原組件中的 created 生命周期函數(shù) component.default.backupCreated.call(this)}} // 表示已經(jīng)注入過了 component.default.attached = truereturn component})}}</script>
上例中,可以看到我們對組件異步加載做了一些特殊的控制,其中 import().then()
則是在加載完子組件的 .js 文件后,實(shí)例化子組件之前的回調(diào),如果需要處理出錯的情況,則 import().then().catch()
即可。
以上代碼只是注入了一個 created
函數(shù),如果要注入其他生命周期函數(shù),例如 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 = truereturn component})}}</script>
通過上面的討論,我們可以做到完全控制 Vue.js 組件的異步加載的全過程,這對于需要精確控制子組件加載的組件,會有很大的幫助。
根據(jù)上面的思路,寫了一個基于 Bootstrap 的異步彈窗演示項(xiàng)目:
https://github.com/hex-ci/vue...
原文地址https://segmentfault.com/a/1190000012427477