溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么vue與element-ui項目中對dialog組件進行封裝

發(fā)布時間:2020-12-14 15:18:28 來源:億速云 閱讀:882 作者:Leah 欄目:開發(fā)技術(shù)

今天就跟大家聊聊有關(guān)怎么vue與element-ui項目中對dialog組件進行封裝,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

1、問題起源

由于 Vue 基于組件化的設計,得益于這個思想,我們在 Vue 的項目中可以通過封裝組件提高代碼的復用性。根據(jù)我目前的使用心得,知道 Vue 拆分組件至少有兩個優(yōu)點:

1、代碼復用。

2、代碼拆分

在基于 element-ui 開發(fā)的項目中,可能我們要寫出一個類似的調(diào)度彈窗功能,很容易編寫出以下代碼:

<template>
  <div>
    <el-dialog :visible.sync="cnMapVisible">我是中國地圖的彈窗</el-dialog>
    <el-dialog :visible.sync="usaMapVisible">我是美國地圖的彈窗</el-dialog>
    <el-dialog :visible.sync="ukMapVisible">我是英國地圖的彈窗</el-dialog>
    <el-button @click="openChina">打開中國地圖</el-button>
    <el-button @click="openUSA">打開美國地圖</el-button>
    <el-button @click="openUK">打開英國地圖</el-button>
  </div>
</template>
<script>
export default {
  name: "View",
  data() {
    return {
      // 對百度地圖和谷歌地圖的一些業(yè)務處理代碼 省略
      cnMapVisible: false,
      usaMapVisible: false,
      ukMapVisible: false,
    };
  },
  methods: {
    // 對百度地圖和谷歌地圖的一些業(yè)務處理代碼 省略
    openChina() {},
    openUSA() {},
    openUK() {},
  },
};
</script>

上述代碼存在的問題非常多,首先當我們的彈窗越來越多的時候,我們會發(fā)現(xiàn)此時需要定義越來越多的變量去控制這個彈窗的顯示或者隱藏。

由于當我們的彈窗的內(nèi)部還有業(yè)務邏輯需要處理,那么此時會有相當多的業(yè)務處理代碼夾雜在一起(比如我調(diào)用中國地圖我需要用高德地圖或者百度地圖,而調(diào)用美國、英國地圖我只能用谷歌地圖,這會使得兩套業(yè)務邏輯分別位于一個文件,嚴重加大了業(yè)務的耦合度)

我們按照分離業(yè)務,降低耦合度的原則,將代碼按以下思路進行拆分:

1、View.vue

<template>
  <div>
    <china-map-dialog ref="china"></china-map-dialog>
    <usa-map-dialog ref="usa"></usa-map-dialog>
    <uk-map-dialog ref="uk"></uk-map-dialog>
    <el-button @click="openChina">打開中國地圖</el-button>
    <el-button @click="openUSA">打開美國地圖</el-button>
    <el-button @click="openUK">打開英國地圖</el-button>
  </div>
</template>
<script>
export default {
  name: "View",
  data() {
    return {
      /**
       將地圖的業(yè)務全部抽離到對應的dialog里面去,View只存放調(diào)度業(yè)務代碼
      */
    };
  },
  methods: {
    openChina() {
      this.$refs.china && this.$refs.china.openDialog();
    },
    openUSA() {
      this.$refs.usa && this.$refs.usa.openDialog();
    },
    openUK() {
      this.$refs.uk && this.$refs.uk.openDialog();
    },
  },
};
</script>

2、ChinaMapDialog.vue

<template>
  <div>
    <el-dialog :visible.sync="baiduMapVisible">我是中國地圖的彈窗</el-dialog>
  </div>
</template>
<script>
export default {
  name: "ChinaMapDialog",
  data() {
    return {
      // 對中國地圖業(yè)務邏輯的封裝處理 省略
      baiduMapVisible: false,
    };
  },
  methods: {
    // 對百度地圖和谷歌地圖的一些業(yè)務處理代碼 省略
    openDialog() {
      this.baiduMapVisible = true;
    },
    closeDialog() {
      this.baiduMapVisible = false;
    },
  },
};
</script>

3、由于此處僅僅展示偽代碼,且和 ChinaMapDialog.vue 表達的含義一致, 為避免篇幅過長 USAMapDialog.vue 和 UKMapDialog.vue 已省略

2、問題分析

我們通過對這幾個彈窗的分析,對剛才的設計進行抽象發(fā)現(xiàn),這里面都有一個共同的部分,那就是我們對 dialog 的操作代碼都是可以重用的代碼,如果我們能夠編寫出一個抽象的彈窗,
然后在恰當?shù)臅r候?qū)⑵浜蜆I(yè)務代碼進行組合,就可以實現(xiàn) 1+1=2 的效果。

3、設計

由于 Vue 在不改變默認的 mixin 原則(默認也最好不要改變,可能會給后來的維護人員帶來困惑)的情況下,如果在混入過程中發(fā)生了命名沖突,默認會將方法合并(數(shù)據(jù)對象在內(nèi)部會進行遞歸合并,并在發(fā)生沖突時以組件數(shù)據(jù)優(yōu)先),因此,mixin 無法改寫本來的實現(xiàn),而我們期望的是,父類提供一個比較抽象的實現(xiàn),子類繼承父類,若子類需要改表這個行為,子類可以重寫父類的方法(多態(tài)的一種實現(xiàn))。

因此我們決定使用 vue-class-component 這個庫,以類的形式來編寫這個抽象彈窗。

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "AbstractDialog",
})
export default class AbstractDialog extends Vue {}

3.1 事件處理

查看 Element-UI 的官方網(wǎng)站,我們發(fā)現(xiàn) ElDialog 對外拋出 4 個事件,因此,我們需要預先接管這 4 個事件。
因此需要在我們的抽象彈窗里預設這個 4 個事件的 handler(因為對于組件的行為的劃分,而對于彈窗的處理本來就應該從屬于彈窗本身,因此我并沒有通過$listeners 去穿透外部調(diào)用時的監(jiān)聽方法)

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "AbstractDialog",
})
export default class AbstractDialog extends Vue {
  open() {
    console.log("彈窗打開,我啥也不做");
  }

  close() {
    console.log("彈窗關(guān)閉,我啥也不做");
  }

  opened() {
    console.log("彈窗打開,我啥也不做");
  }

  closed() {
    console.log("彈窗關(guān)閉,我啥也不做");
  }
}

3.2 屬性處理

dialog 有很多屬性,默認我們只需要關(guān)注的是 before-close 和 title 兩者,因為這兩個屬性從職責上劃分是從屬于彈窗本身的行為,所以我們會在抽象彈窗里面處理開關(guān)和 title 的任務

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "AbstractDialog",
})
export default class AbstractDialog extends Vue {
  visible = false;

  t = "";

  loading = false;

  //定義這個屬性的目的是為了實現(xiàn)既可以外界通過傳入屬性改變dialog的屬性,也支持組件內(nèi)部預設dialog的屬性
  attrs = {};

  get title() {
    return this.t;
  }

  setTitle(title) {
    this.t = title;
  }
}

3.3 slots 的處理

查看 Element-UI 的官方網(wǎng)站,我們發(fā)現(xiàn),ElDialog 有三個插槽,因此,我們需要接管這三個插槽

1、對 header 的處理

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "AbstractDialog",
})
class AbstractDialog extends Vue {
  /*
   構(gòu)建彈窗的Header
   */
  _createHeader(h) {
    // 判斷在調(diào)用的時候,外界是否傳入header的插槽,若有的話,則以外界傳入的插槽為準
    var slotHeader = this.$scopedSlots["header"] || this.$slots["header"];
    if (typeof slotHeader === "function") {
      return slotHeader();
    }
    //若用戶沒有傳入插槽,則判斷用戶是否想改寫Header
    var renderHeader = this.renderHeader;
    if (typeof renderHeader === "function") {
      return <div slot="header">{renderHeader(h)}</div>;
    }
    //如果都沒有的話, 返回undefined,則dialog會使用我們預設好的title
  }
}

2、對 body 的處理

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "AbstractDialog",
})
class AbstractDialog extends Vue {
  /**
   * 構(gòu)建彈窗的Body部分
   */
  _createBody(h) {
    // 判斷在調(diào)用的時候,外界是否傳入default的插槽,若有的話,則以外界傳入的插槽為準
    var slotBody = this.$scopedSlots["default"] || this.$slots["default"];
    if (typeof slotBody === "function") {
      return slotBody();
    }
    //若用戶沒有傳入插槽,則判斷用戶想插入到body部分的內(nèi)容
    var renderBody = this.renderBody;
    if (typeof renderBody === "function") {
      return renderBody(h);
    }
  }
}

3、對 footer 的處理

由于 dialog 的 footer 經(jīng)常都有一些相似的業(yè)務,因此,我們需要把這些重復率高的代碼封裝在此,若在某種時候,用戶需要改寫 footer 的時候,再重寫,否則使用默認行為

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "BaseDialog",
})
export default class BaseDialog extends Vue {
  showLoading() {
    this.loading = true;
  }

  closeLoading() {
    this.loading = false;
  }

  onSubmit() {
    this.closeDialog();
  }

  onClose() {
    this.closeDialog();
  }

  /**
   * 構(gòu)建彈窗的Footer
   */
  _createFooter(h) {
    var footer = this.$scopedSlots.footer || this.$slots.footer;
    if (typeof footer == "function") {
      return footer();
    }
    var renderFooter = this.renderFooter;
    if (typeof renderFooter === "function") {
      return <div slot="footer">{renderFooter(h)}</div>;
    }

    return this.defaultFooter(h);
  }

  defaultFooter(h) {
    return (
      <div slot="footer">
        <el-button
          type="primary"
          loading={this.loading}
          on-click={() => {
            this.onSubmit();
          }}
        >
          保存
        </el-button>
        <el-button
          on-click={() => {
            this.onClose();
          }}
        >
          取消
        </el-button>
      </div>
    );
  }
}

最后,我們再通過 JSX 將我們編寫的這些代碼組織起來,就得到了我們最終想要的抽象彈窗
代碼如下:

import Vue from "vue";
import Component from "vue-class-component";
@Component({
  name: "AbstractDialog",
})
export default class AbstractDialog extends Vue {
  visible = false;

  t = "";

  loading = false;

  attrs = {};

  get title() {
    return this.t;
  }

  setTitle(title) {
    this.t = title;
  }

  open() {
    console.log("彈窗打開,我啥也不做");
  }

  close() {
    console.log("彈窗關(guān)閉,我啥也不做");
  }

  opened() {
    console.log("彈窗打開,我啥也不做");
  }

  closed() {
    console.log("彈窗關(guān)閉,我啥也不做");
  }

  showLoading() {
    this.loading = true;
  }

  closeLoading() {
    this.loading = false;
  }

  openDialog() {
    this.visible = true;
  }

  closeDialog() {
    if (this.loading) {
      this.$message.warning("請等待操作完成!");
      return;
    }
    this.visible = false;
  }

  onSubmit() {
    this.closeDialog();
  }

  onClose() {
    this.closeDialog();
  }

  /*
   構(gòu)建彈窗的Header
   */
  _createHeader(h) {
    var slotHeader = this.$scopedSlots["header"] || this.$slots["header"];
    if (typeof slotHeader === "function") {
      return slotHeader();
    }
    var renderHeader = this.renderHeader;
    if (typeof renderHeader === "function") {
      return <div slot="header">{renderHeader(h)}</div>;
    }
  }

  /**
   * 構(gòu)建彈窗的Body部分
   */
  _createBody(h) {
    var slotBody = this.$scopedSlots["default"] || this.$slots["default"];
    if (typeof slotBody === "function") {
      return slotBody();
    }
    var renderBody = this.renderBody;
    if (typeof renderBody === "function") {
      return renderBody(h);
    }
  }

  /**
   * 構(gòu)建彈窗的Footer
   */
  _createFooter(h) {
    var footer = this.$scopedSlots.footer || this.$slots.footer;
    if (typeof footer == "function") {
      return footer();
    }
    var renderFooter = this.renderFooter;
    if (typeof renderFooter === "function") {
      return <div slot="footer">{renderFooter(h)}</div>;
    }

    return this.defaultFooter(h);
  }

  defaultFooter(h) {
    return (
      <div slot="footer">
        <el-button
          type="primary"
          loading={this.loading}
          on-click={() => {
            this.onSubmit();
          }}
        >
          保存
        </el-button>
        <el-button
          on-click={() => {
            this.onClose();
          }}
        >
          取消
        </el-button>
      </div>
    );
  }

  createContainer(h) {
    //防止外界誤傳參數(shù)影響彈窗本來的設計,因此,需要將某些參數(shù)過濾開來,有title beforeClose, visible
    var { title, beforeClose, visible, ...rest } = Object.assign({}, this.$attrs, this.attrs);
    return (
      <el-dialog
        {...{
          props: {
            ...rest,
            visible: this.visible,
            title: this.title || title || "彈窗",
            beforeClose: this.closeDialog,
          },
          on: {
            close: this.close,
            closed: this.closed,
            opened: this.opened,
            open: this.open,
          },
        }}
      >
        {/* 根據(jù)JSX的渲染規(guī)則 null、 undefined、 false、 '' 等內(nèi)容將不會在頁面顯示,若createHeader返回undefined,將會使用默認的title */}
        {this._createHeader(h)}
        {this._createBody(h)}
        {this._createFooter(h)}
      </el-dialog>
    );
  }

  render(h) {
    return this.createContainer(h);
  }
}

4.應用

4.1組件調(diào)用

我們就以編寫 ChinaMapDialog.vue 為例,將其進行改寫

<script>
import Vue from "vue";
import AbstractDialog from "@/components/AbstractDialog.vue";
import Component from "vue-class-component";
@Component({
  name: "ChinaMapDialog",
})
class ChinaMapDialog extends AbstractDialog {
  get title() {
    return "這是中國地圖";
  }
  
  attrs = {
   width: "600px",
  }

  //編寫一些中國地圖的處理業(yè)務邏輯代碼

  //編寫彈窗的內(nèi)容部分
  renderBody(h) {
    return <div>我是中國地圖,我講為你呈現(xiàn)華夏最壯麗的美</div>;
  }
}
</script>

4.2 使用 Composition API

由于我們是通過組件的實例調(diào)用組件的方法,因此我們每次都需要獲取當前組件的 refs 上面的屬性,這樣會使得我們的調(diào)用特別長,寫起來也特別麻煩。
我們可以通過使用 Composition API 來簡化這個寫法

<template>
  <div>
    <china-map-dialog ref="china"></china-map-dialog>
    <usa-map-dialog ref="usa"></usa-map-dialog>
    <uk-map-dialog ref="uk"></uk-map-dialog>
    <el-button @click="openChina">打開中國地圖</el-button>
    <el-button @click="openUSA">打開美國地圖</el-button>
    <el-button @click="openUK">打開英國地圖</el-button>
  </div>
</template>
<script>
import { ref } from "@vue/composition-api";
export default {
  name: "View",
  setup() {
    const china = ref(null);
    const usa = ref(null);
    const uk = ref(null);
    return {
      china,
      usa,
      uk,
    };
  },
  data() {
    return {
      /**
       將地圖的業(yè)務全部抽離到對應的dialog里面去,View只存放調(diào)度業(yè)務代碼
      */
    };
  },
  methods: {
    // 對百度地圖和谷歌地圖的一些業(yè)務處理代碼 省略
    openChina() {
      this.china && this.china.openDialog();
    },
    openUSA() {
      this.usa && this.usa.openDialog();
    },
    openUK() {
      this.uk && this.uk.openDialog();
    },
  },
};
</script>

總結(jié)

開發(fā)這個彈窗所用到的知識點:
1、面向?qū)ο笤O計在前端開發(fā)中的應用;
2、如何編寫基于類風格的組件(vue-class-component 或 vue-property-decorator);
3、JSX 在 vue 中的應用;
4、$attrs和$listeners 在開發(fā)高階組件(個人叫法)中的應用;
5、slots 插槽,以及插槽在 JSX 中的用法;
6、在 Vue2.x 中使用 Composition API;

看完上述內(nèi)容,你們對怎么vue與element-ui項目中對dialog組件進行封裝有進一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI