在一些特殊場景下,使用組件的時機無法確定,或者無法在Vue的template中確定要我們要使用的組件,這時就需要動態的掛載組件,或者使用運行時編譯動態創建組件並掛載。
今天我們將帶大家從實際項目出發,看看在實際解決客戶問題時,如何將組件進行動態掛載,並為大家展示一個完整的解決動態掛載問題的完整過程。
無法解決的“動態掛載”
我們的電子表格控件SpreadJS在運行時,存在這樣一個功能:當用戶雙擊單元格會顯示一個輸入框用於編輯單元格的內容,用戶可以根據需求按照自定義單元格類型的規范自定義輸入框的形式,集成任何Form表單輸入類型。
這個輸入框的創建銷毀都是通過繼承單元格類型對應方法實現的,因此這里就存在一個問題——這個動態的創建方式並不能簡單在VUE template中配置,然后直接使用。
而就在前不久,客戶問然詢問我:你家控件的自定義單元格是否支持Vue組件比如ElementUI的AutoComplete?
由於前面提到的這個問題:
沉思許久,我認真給客戶回復:“組件運行生命周期不一致,用不了”,但又話鋒一轉,表示可以使用通用組件解決這個問題。
問題呢,是順利解決了。
但是這個無奈的"用不了",卻也成為我這幾天午夜夢回跨不去的坎。
后來,某天看Vue文檔時,我想到App是運行時掛載到#app上的。,從理論上來說,其他組件也應該能動態掛載到需要的Dom上,這樣創建時機的問題不就解決了嘛!
正式開啟動態掛載
讓我們繼續查看文檔,全局APIVue.extend( options )是通過extend創建的。Vue實例可以使用$mount方法直接掛載到DOM元素上——這正是我們需要的。
<div id="mount-point"></div>
// 創建構造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 創建 Profile 實例,並掛載到一個元素上。
new Profile().$mount('#mount-point')
按照SpreadJS自定義單元格示例創建AutoCompleteCellType,並設置到單元格中:
function AutoComplateCellType() {
}
AutoComplateCellType.prototype = new GC.Spread.Sheets.CellTypes.Base();
AutoComplateCellType.prototype.createEditorElement = function (context, cellWrapperElement) {
// cellWrapperElement.setAttribute("gcUIElement", "gcEditingInput");
cellWrapperElement.style.overflow = 'visible'
let editorContext = document.createElement("div")
editorContext.setAttribute("gcUIElement", "gcEditingInput");
let editor = document.createElement("div");
// 自定義單元格中editorContext作為容器,需要在創建一個child用於掛載,不能直接掛載到editorContext上
editorContext.appendChild(editor);
return editorContext;
}
AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
let width = cellRect.width > 180 ? cellRect.width : 180;
if (editorContext) {
// 創建構造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 創建 Profile 實例,並掛載到一個元素上。
new Profile().$mount(editorContext.firstChild);
}
};
運行,雙擊進入編輯狀態,結果卻發現報錯了
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
根據報錯提示,此時候我們有兩種解決辦法:
- 開啟runtimeCompiler,在vue.config.js中加入runtimeCompiler: true的配置,允許運行時編譯,這樣可以動態生成template,滿足動態組件的需求
- 提前編譯模板僅動態掛載,autocomplete的組件是確定的,我們可以使用這種方法
新建AutoComplete.vue組件用於動態掛載,這樣可以掛載編譯好的組件。
<template>
<div>
<p>{{ firstName }} {{ lastName }} aka {{ alias }}</p>
</div>
</template>
<script>
export default {
data: function () {
return {
firstName: "Walter",
lastName: "White",
alias: "Heisenberg",
};
},
};
</script>
import AutoComplate from './AutoComplate.vue'
AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
let width = cellRect.width > 180 ? cellRect.width : 180;
if (editorContext) {
// 創建構造器
var Profile = Vue.extend(AutoComplate);
// 創建 Profile 實例,並掛載到一個元素上。
new Profile().$mount(editorContext.firstChild);
}
};
雙擊進入編輯狀態,看到組件中的內容
下一步,對於自定義單元格還需要設置和獲取組件中的編輯內容,這時通過給組件添加props,同時在掛載時創建的VueComponent實例上直接獲取到所有props內容,對應操作即可實現數據獲取設置。
更新AutoComplate.vue,添加props,增加input用於編輯
<template>
<div>
<p>{{ firstName }} {{ lastName }} aka {{ alias }}</p>
<input type="text" v-model="value">
</div>
</template>
<script>
export default {
props:["value"],
data: function () {
return {
firstName: "Walter",
lastName: "White",
alias: "Heisenberg",
};
},
};
</script>
通過this.vm存儲VueComponent實例,在getEditorValue 和setEditorValue 方法中獲取和給VUE組件設置Value。編輯結束,通過調用$destroy()方法銷毀動態創建的組件。
AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
let width = cellRect.width > 180 ? cellRect.width : 180;
if (editorContext) {
// 創建構造器
var Profile = Vue.extend(MyInput);
// 創建 Profile 實例,並掛載到一個元素上。
this.vm = new Profile().$mount(editorContext.firstChild);
}
};
AutoComplateCellType.prototype.getEditorValue = function (editorContext) {
// 設置組件默認值
if (this.vm) {
return this.vm.value;
}
};
AutoComplateCellType.prototype.setEditorValue = function (editorContext, value) {
// 獲取組件編輯后的值
if (editorContext) {
this.vm.value = value;
}
};
AutoComplateCellType.prototype.deactivateEditor = function (editorContext, context) {
// 銷毀組件
this.vm.$destroy();
this.vm = undefined;
};
整個流程跑通了,下來只需要在AutoComplate.vue中,將input替換成ElementUI 的el- autocomplete並實現對應方法就好了。
結果
讓我們看看效果吧。
其實動態掛載並不是什么復雜操作,理解了Vue示例,通過vm來操作實例,靈活的運用動態掛載或者運行時編譯的組件就不是什么難事了。
其實一切的解決方案就在Vue教程入門教程中,但是腳手架的使用和各種工具的使用讓我們忘記了Vue的初心,反而把簡單問題復雜化了。
今天的分享到這里就結束啦,后續還會為大家帶來更多嚴肅和有趣的內容~
你有什么在開發中"忘記初心"的事情嗎?