靈光一閃!幫你使用Vue,搞定無法解決的“動態掛載”

葡萄城技術團隊發表於2021-10-27

在一些特殊場景下,使用元件的時機無法確定,或者無法在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.

根據報錯提示,此時候我們有兩種解決辦法:

  1. 開啟runtimeCompiler,在vue.config.js中加入runtimeCompiler: true的配置,允許執行時編譯,這樣可以動態生成template,滿足動態元件的需求
  2. 提前編譯模板僅動態掛載,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的初心,反而把簡單問題複雜化了。

今天的分享到這裡就結束啦,後續還會為大家帶來更多嚴肅和有趣的內容~

你有什麼在開發中"忘記初心"的事情嗎?

相關文章