詳解如何在vue+element-ui的項(xiàng)目中封裝dialog組件
1、問(wèn)題起源
由于 Vue 基于組件化的設(shè)計(jì),得益于這個(gè)思想,我們?cè)?Vue 的項(xiàng)目中可以通過(guò)封裝組件提高代碼的復(fù)用性。根據(jù)我目前的使用心得,知道 Vue 拆分組件至少有兩個(gè)優(yōu)點(diǎn):
1、代碼復(fù)用。
2、代碼拆分
在基于 element-ui 開(kāi)發(fā)的項(xiàng)目中,可能我們要寫(xiě)出一個(gè)類(lèi)似的調(diào)度彈窗功能,很容易編寫(xiě)出以下代碼:
<template> <div> <el-dialog :visible.sync="cnMapVisible">我是中國(guó)地圖的彈窗</el-dialog> <el-dialog :visible.sync="usaMapVisible">我是美國(guó)地圖的彈窗</el-dialog> <el-dialog :visible.sync="ukMapVisible">我是英國(guó)地圖的彈窗</el-dialog> <el-button @click="openChina">打開(kāi)中國(guó)地圖</el-button> <el-button @click="openUSA">打開(kāi)美國(guó)地圖</el-button> <el-button @click="openUK">打開(kāi)英國(guó)地圖</el-button> </div> </template> <script> export default { name: "View", data() { return { // 對(duì)百度地圖和谷歌地圖的一些業(yè)務(wù)處理代碼 省略 cnMapVisible: false, usaMapVisible: false, ukMapVisible: false, }; }, methods: { // 對(duì)百度地圖和谷歌地圖的一些業(yè)務(wù)處理代碼 省略 openChina() {}, openUSA() {}, openUK() {}, }, }; </script>
上述代碼存在的問(wèn)題非常多,首先當(dāng)我們的彈窗越來(lái)越多的時(shí)候,我們會(huì)發(fā)現(xiàn)此時(shí)需要定義越來(lái)越多的變量去控制這個(gè)彈窗的顯示或者隱藏。
由于當(dāng)我們的彈窗的內(nèi)部還有業(yè)務(wù)邏輯需要處理,那么此時(shí)會(huì)有相當(dāng)多的業(yè)務(wù)處理代碼夾雜在一起(比如我調(diào)用中國(guó)地圖我需要用高德地圖或者百度地圖,而調(diào)用美國(guó)、英國(guó)地圖我只能用谷歌地圖,這會(huì)使得兩套業(yè)務(wù)邏輯分別位于一個(gè)文件,嚴(yán)重加大了業(yè)務(wù)的耦合度)
我們按照分離業(yè)務(wù),降低耦合度的原則,將代碼按以下思路進(jìn)行拆分:
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">打開(kāi)中國(guó)地圖</el-button> <el-button @click="openUSA">打開(kāi)美國(guó)地圖</el-button> <el-button @click="openUK">打開(kāi)英國(guó)地圖</el-button> </div> </template> <script> export default { name: "View", data() { return { /** 將地圖的業(yè)務(wù)全部抽離到對(duì)應(yīng)的dialog里面去,View只存放調(diào)度業(yè)務(wù)代碼 */ }; }, 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">我是中國(guó)地圖的彈窗</el-dialog> </div> </template> <script> export default { name: "ChinaMapDialog", data() { return { // 對(duì)中國(guó)地圖業(yè)務(wù)邏輯的封裝處理 省略 baiduMapVisible: false, }; }, methods: { // 對(duì)百度地圖和谷歌地圖的一些業(yè)務(wù)處理代碼 省略 openDialog() { this.baiduMapVisible = true; }, closeDialog() { this.baiduMapVisible = false; }, }, }; </script>
3、由于此處僅僅展示偽代碼,且和 ChinaMapDialog.vue 表達(dá)的含義一致, 為避免篇幅過(guò)長(zhǎng) USAMapDialog.vue 和 UKMapDialog.vue 已省略
2、問(wèn)題分析
我們通過(guò)對(duì)這幾個(gè)彈窗的分析,對(duì)剛才的設(shè)計(jì)進(jìn)行抽象發(fā)現(xiàn),這里面都有一個(gè)共同的部分,那就是我們對(duì) dialog 的操作代碼都是可以重用的代碼,如果我們能夠編寫(xiě)出一個(gè)抽象的彈窗,
然后在恰當(dāng)?shù)臅r(shí)候?qū)⑵浜蜆I(yè)務(wù)代碼進(jìn)行組合,就可以實(shí)現(xiàn) 1+1=2 的效果。
3、設(shè)計(jì)
由于 Vue 在不改變默認(rèn)的 mixin 原則(默認(rèn)也最好不要改變,可能會(huì)給后來(lái)的維護(hù)人員帶來(lái)困惑)的情況下,如果在混入過(guò)程中發(fā)生了命名沖突,默認(rèn)會(huì)將方法合并(數(shù)據(jù)對(duì)象在內(nèi)部會(huì)進(jìn)行遞歸合并,并在發(fā)生沖突時(shí)以組件數(shù)據(jù)優(yōu)先),因此,mixin 無(wú)法改寫(xiě)本來(lái)的實(shí)現(xiàn),而我們期望的是,父類(lèi)提供一個(gè)比較抽象的實(shí)現(xiàn),子類(lèi)繼承父類(lèi),若子類(lèi)需要改表這個(gè)行為,子類(lèi)可以重寫(xiě)父類(lèi)的方法(多態(tài)的一種實(shí)現(xiàn))。
因此我們決定使用 vue-class-component 這個(gè)庫(kù),以類(lèi)的形式來(lái)編寫(xiě)這個(gè)抽象彈窗。
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 對(duì)外拋出 4 個(gè)事件,因此,我們需要預(yù)先接管這 4 個(gè)事件。
因此需要在我們的抽象彈窗里預(yù)設(shè)這個(gè) 4 個(gè)事件的 handler(因?yàn)閷?duì)于組件的行為的劃分,而對(duì)于彈窗的處理本來(lái)就應(yīng)該從屬于彈窗本身,因此我并沒(méi)有通過(guò)$listeners 去穿透外部調(diào)用時(shí)的監(jiān)聽(tīng)方法)
import Vue from "vue"; import Component from "vue-class-component"; @Component({ name: "AbstractDialog", }) export default class AbstractDialog extends Vue { open() { console.log("彈窗打開(kāi),我啥也不做"); } close() { console.log("彈窗關(guān)閉,我啥也不做"); } opened() { console.log("彈窗打開(kāi),我啥也不做"); } closed() { console.log("彈窗關(guān)閉,我啥也不做"); } }
3.2 屬性處理
dialog 有很多屬性,默認(rèn)我們只需要關(guān)注的是 before-close 和 title 兩者,因?yàn)檫@兩個(gè)屬性從職責(zé)上劃分是從屬于彈窗本身的行為,所以我們會(huì)在抽象彈窗里面處理開(kāi)關(guān)和 title 的任務(wù)
import Vue from "vue"; import Component from "vue-class-component"; @Component({ name: "AbstractDialog", }) export default class AbstractDialog extends Vue { visible = false; t = ""; loading = false; //定義這個(gè)屬性的目的是為了實(shí)現(xiàn)既可以外界通過(guò)傳入屬性改變dialog的屬性,也支持組件內(nèi)部預(yù)設(shè)dialog的屬性 attrs = {}; get title() { return this.t; } setTitle(title) { this.t = title; } }
3.3 slots 的處理
查看 Element-UI 的官方網(wǎng)站,我們發(fā)現(xiàn),ElDialog 有三個(gè)插槽,因此,我們需要接管這三個(gè)插槽
1、對(duì) 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)用的時(shí)候,外界是否傳入header的插槽,若有的話(huà),則以外界傳入的插槽為準(zhǔn) var slotHeader = this.$scopedSlots["header"] || this.$slots["header"]; if (typeof slotHeader === "function") { return slotHeader(); } //若用戶(hù)沒(méi)有傳入插槽,則判斷用戶(hù)是否想改寫(xiě)Header var renderHeader = this.renderHeader; if (typeof renderHeader === "function") { return <div slot="header">{renderHeader(h)}</div>; } //如果都沒(méi)有的話(huà), 返回undefined,則dialog會(huì)使用我們預(yù)設(shè)好的title } }
2、對(duì) 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)用的時(shí)候,外界是否傳入default的插槽,若有的話(huà),則以外界傳入的插槽為準(zhǔn) var slotBody = this.$scopedSlots["default"] || this.$slots["default"]; if (typeof slotBody === "function") { return slotBody(); } //若用戶(hù)沒(méi)有傳入插槽,則判斷用戶(hù)想插入到body部分的內(nèi)容 var renderBody = this.renderBody; if (typeof renderBody === "function") { return renderBody(h); } } }
3、對(duì) footer 的處理
由于 dialog 的 footer 經(jīng)常都有一些相似的業(yè)務(wù),因此,我們需要把這些重復(fù)率高的代碼封裝在此,若在某種時(shí)候,用戶(hù)需要改寫(xiě) footer 的時(shí)候,再重寫(xiě),否則使用默認(rèn)行為
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> ); } }
最后,我們?cè)偻ㄟ^(guò) JSX 將我們編寫(xiě)的這些代碼組織起來(lái),就得到了我們最終想要的抽象彈窗
代碼如下:
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("彈窗打開(kāi),我啥也不做"); } close() { console.log("彈窗關(guān)閉,我啥也不做"); } opened() { console.log("彈窗打開(kāi),我啥也不做"); } closed() { console.log("彈窗關(guān)閉,我啥也不做"); } showLoading() { this.loading = true; } closeLoading() { this.loading = false; } openDialog() { this.visible = true; } closeDialog() { if (this.loading) { this.$message.warning("請(qǐng)等待操作完成!"); 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ù)影響彈窗本來(lái)的設(shè)計(jì),因此,需要將某些參數(shù)過(guò)濾開(kāi)來(lái),有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)容將不會(huì)在頁(yè)面顯示,若createHeader返回undefined,將會(huì)使用默認(rèn)的title */} {this._createHeader(h)} {this._createBody(h)} {this._createFooter(h)} </el-dialog> ); } render(h) { return this.createContainer(h); } }
4.應(yīng)用
4.1組件調(diào)用
我們就以編寫(xiě) ChinaMapDialog.vue 為例,將其進(jìn)行改寫(xiě)
<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 "這是中國(guó)地圖"; } attrs = { width: "600px", } //編寫(xiě)一些中國(guó)地圖的處理業(yè)務(wù)邏輯代碼 //編寫(xiě)彈窗的內(nèi)容部分 renderBody(h) { return <div>我是中國(guó)地圖,我講為你呈現(xiàn)華夏最壯麗的美</div>; } } </script>
4.2 使用 Composition API
由于我們是通過(guò)組件的實(shí)例調(diào)用組件的方法,因此我們每次都需要獲取當(dāng)前組件的 refs 上面的屬性,這樣會(huì)使得我們的調(diào)用特別長(zhǎng),寫(xiě)起來(lái)也特別麻煩。
我們可以通過(guò)使用 Composition API 來(lái)簡(jiǎn)化這個(gè)寫(xiě)法
<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">打開(kāi)中國(guó)地圖</el-button> <el-button @click="openUSA">打開(kāi)美國(guó)地圖</el-button> <el-button @click="openUK">打開(kāi)英國(guó)地圖</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è)務(wù)全部抽離到對(duì)應(yīng)的dialog里面去,View只存放調(diào)度業(yè)務(wù)代碼 */ }; }, methods: { // 對(duì)百度地圖和谷歌地圖的一些業(yè)務(wù)處理代碼 省略 openChina() { this.china && this.china.openDialog(); }, openUSA() { this.usa && this.usa.openDialog(); }, openUK() { this.uk && this.uk.openDialog(); }, }, }; </script>
總結(jié)
開(kāi)發(fā)這個(gè)彈窗所用到的知識(shí)點(diǎn):
1、面向?qū)ο笤O(shè)計(jì)在前端開(kāi)發(fā)中的應(yīng)用;
2、如何編寫(xiě)基于類(lèi)風(fēng)格的組件(vue-class-component 或 vue-property-decorator);
3、JSX 在 vue 中的應(yīng)用;
4、$attrs和$listeners 在開(kāi)發(fā)高階組件(個(gè)人叫法)中的應(yīng)用;
5、slots 插槽,以及插槽在 JSX 中的用法;
6、在 Vue2.x 中使用 Composition API;
到此這篇關(guān)于詳解如何在vue+element-ui的項(xiàng)目中封裝dialog組件的文章就介紹到這了,更多相關(guān)vue element封裝dialog內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue?報(bào)錯(cuò)-4058?ENOENT:no?such?file?or?directory的原因及解決方法
Vue?報(bào)錯(cuò)-4058?ENOENT:?no?such?file?or?directory的原因和解決辦法,關(guān)于為什么為會(huì)報(bào)這個(gè)錯(cuò)誤,按照字面意思的理解就是沒(méi)有找到這個(gè)文件或這個(gè)路徑,說(shuō)明是路徑不對(duì),本文給大家分享解決方案,感興趣的朋友一起看看吧2023-10-10vue元素實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡效果
這篇文章主要介紹了vue元素實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡效果,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07解決vue?vite啟動(dòng)項(xiàng)目報(bào)錯(cuò)ERROR:?Unexpected?“\x88“?in?JSON?的問(wèn)題
這篇文章主要介紹了vue?vite啟動(dòng)項(xiàng)目報(bào)錯(cuò)ERROR:?Unexpected?“\x88“?in?JSON?原因,本文給出出現(xiàn)此類(lèi)問(wèn)題的原因所在并給出解決方法,需要的朋友可以參考下2022-09-09vue :src 文件路徑錯(cuò)誤問(wèn)題的解決方法
這篇文章主要介紹了vue :src 文件路徑錯(cuò)誤問(wèn)題的簡(jiǎn)單解決方法,本文分步驟給大家介紹的非常詳細(xì),感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-05-05詳解@Vue/Cli 3 Invalid Host header 錯(cuò)誤解決辦法
這篇文章主要介紹了詳解@Vue/Cli 3 Invalid Host header 錯(cuò)誤解決辦法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01vue-cli與webpack處理靜態(tài)資源的方法及webpack打包的坑
這篇文章主要介紹了vue-cli與webpack處理靜態(tài)資源的方法,需要的朋友可以參考下2018-05-05vue下如何利用canvas實(shí)現(xiàn)在線(xiàn)圖片標(biāo)注
這篇文章主要介紹了vue下如何利用canvas實(shí)現(xiàn)在線(xiàn)圖片標(biāo)注,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04