vue全局彈窗通用解決方案——組件函數化

我們在vue項目開發過程中總是遇到各種個樣的全局彈窗(權限提醒,alert提示,繳費提醒),這些全局彈窗全站很多地方在調用,調用調用很蠻煩,將這些組件部署到全局位置,又無法做到按需加載,而且部署到全局的組件數據回傳(比如檢測繳費是否完成)又是一個問題,我們又得借助vuex或者event來傳遞,又回造成vuex的濫用。

我們舉一個簡單的場景:
如果我們要在vue里實現一個類似原生alert的彈出框,大概長這個樣子。

<template>
    <div class="popup_background">
        <div>
            <div v-html="title"></div>
            <button @click="ok">確定</button>
            <button @click="cancel">否定</button>
        </div>
    </div>
</template>
<script>
export default {
    props: ['text'],
    data() {
        return {};
    },
    methods: {
        ok() {
            this.$emit('ok');
        },
        cancel() {
            this.$emit('cancel');
        }
    }
}
</script>

如果有一個頁面,需要使用這個組件,即使是空頁面,也要做下面代碼里描述的所有事情

<template>
    <div>
        <button @click="showAlert">打開alert</button>
        <ui-alert v-if="isShow" title="這是標題" @ok="fine" @cancel="cancel"></ui-alert>
    </div>
</template>
<script>
import uiAlert from '******.vue';  // 引用組件
export default {
    data() {
        return {
            isShow:false  //控制彈窗是否顯示的開關
        };
    },
    methods: {
        showAlert() {  //控制彈窗顯示
            this.isShow = true;
        },
        fine() {  //彈窗打開后點擊ok的事件綁定
            this.isShow = false;
            // 點擊ok觸發的事情
        },
        cancel() {  //彈窗打開后點擊cancel的事件綁定
            this.isShow = false;
            // 點擊cancel觸發的事情
        }
    },
    components:{uiAlert}
}
</script>

匯總一下,做的事情有
1、引用組件
2、設置控制顯示的變量開關
3、設置彈出組件的函數
4、設置多個回調事件,監聽返回值

但是我們想一下,假如我們可以像調用原生組件類似的形式,一個函數搞定,會更方便。獲取返回值的方式比如promise、回調。比如我們封裝成返回promise的一個函數,代碼可能像下面這樣。

<template>
    <div>
        <button @click="showAlert">打開alert</button>
    </div>
</template>
<script>
import popAlert from '*******';
export default {
    methods: {
        showAlert() {
              popAlert('這是標題').then(res=>{
                      // res 就是true或者false,代表點擊了ok還是cancel
              })
        }
    }
}
</script>

這樣調用起來很方便,我們可以看看類似alert這樣的彈出框,有一些特點,就是過程化,它從被創建到使用,再到最后銷毀,這個過程相對調用的頁面是獨立的,交互過程是高度過程化的。不像其他組件和頁面直接可能多次交互。簡單來說,就是“用完即刪”。再有就是布局獨立,布局層面跟調用者不存在占位歸屬,更像是全局的組件。
只要符合這種特點的組件,封裝成函數,是更方便的策略。

那么如何封裝出一個類似剛才例子中popAlert這樣的函數呢?我們要保留組件要使用vue的開發方式,也就是說,我們要把已經存在的vue組件不進行任何改動,在組件外對它通過包裝的形式形成一個函數。這樣vue組件可以向前兼容,并且組件繼續使用vue的開發方式。

全局按需動態加載

我想到的實現方式,就是利用render函數。我們在一個“全局”的地方,所有頁面都會加載的最外層的模板,比如跟路由配置的那個App.vue。在這里添加我們要開發的,可以動態改變狀態的組件。

<template>
    <div id="app">
        <router-view/>
        <!--全局的地方添加一個組件-->
        <dynamic-vue></dynamic-vue>
    </div>
</template>
.........

我們看一下dynamic-vue的實現

<!--一個用于創建臨時vue對象的容器,例如選擇彈窗,提示窗,或其他場景下使用的"用完即刪"型需求-->
<script>
export default {
    data() {
        return {
            globleVue: null // 臨時動態添加的窗體,例如彈窗等
            /*
            {
                vueObj: 'vue組件',
                attr:{  //初始化組件的props
                },
                on:{  //組件的emit回調綁定
                }
            }
            */
        }
    },
    render(createElement) {
        if(this.globleVue){
            return createElement(this.globleVue.vueObj, {
                props: this.globleVue.attr,
                on: this.globleVue.event
            });
        }
        else {
            return createElement('div');
        }
    }
}
</script>

接下來我們只要能夠通過某些方法把globleVue賦值成包含vue組件的對象,初始化的參數,回調方法的對象,全局就會彈出這個組件。我的實現方式是使用自帶的events對globleVue進行修改。我們在created里面增加event的監聽。

<script>
import { dynamicVueEvent } from 'dynamicVueObject.js';
export default {
    created() {
        // 支持全局調用臨時添加,用完即刪型vue組件
        dynamicVueEvent.on('addVueObj', (vueObj, attr, event) => {
            this.globleVue = {
                vueObj,
                attr,
                event
            };
        });
        dynamicVueEvent.on('deleteVueObj', () => {
            this.globleVue = null;
        });
    },
    data() {
        return {
            globleVue: null
        }
    },
    render(createElement) {
        if(this.globleVue){
            return createElement(this.globleVue.vueObj, {
                props: this.globleVue.attr,
                on: this.globleVue.event
            });
        }
        else {
            return createElement('div');
        }
    }
}
</script>

觸發 dynamicVueEvent 事件的代碼。

// dynamicVueObject.js
import Vue from 'vue';
var events = require('events');
var eventEmitter = new events.EventEmitter();
export function createVueObj(vueClass, attr, event) {
    let vueObj = Vue.extend(vueClass);
    dynamicVueEvent.emit('addVueObj', vueObj, attr, event);
}
export function deleteVueObj() {
    dynamicVueEvent.emit('deleteVueObj');
}

dynamicVueObject.js暴露了兩個函數,一個動態創建組件createVueObj,一個清空組件deleteVueObj。我們就有了在全局動態控制彈出組件的這個功能。我們可以在任何地方通過這兩個函數控制全局彈窗了。

接下來我們就可以對已經存在的alert.vue組件,不進行修改的基礎上,單獨封裝一個獨立的函數。


image.png

我們在原始的alert.vue旁邊創建一個alert.js,這樣調用者很容易發現和調用。

/*
    alert.js
    alert.vue組件函數化
*/
// 引入全局創建組件,和全局刪除組件兩個方法
import { createVueObj, deleteVueObj } from '../dynamicVueObject/dynamicVueObject';
import alertVue from './alert.vue'; // 引入要使用的組件

export function alert(text) {
    return new Promise((resolve, reject) => {
        let dyVueObj = createVueObj(alertVue, {
            text
        }, {
            ok() {
                resolve(true);
                deleteVueObj(dyVueObj); // 刪除掉彈出的組件
            },
            cancel() {
                resolve(false);
                deleteVueObj(dyVueObj); // 刪除掉彈出的組件
            }
        });
    });
}

這樣alert函數封裝完畢了。不到20行就可以針對alert.vue封裝一個promise方式的alert,很方便,接下來我們就可以調用一下這個alert函數

<template>
    <div>
        <button @click="showAlert">打開alert</button>
    </div>
</template>
<script>
import popAlert from '****/alert.js';
export default {
    methods: {
        showAlert() {
              popAlert('這是標題').then(res=>{
                      // res 就是true或者false,代表點擊了ok還是cancel
              })
        }
    }
}
</script>

這樣調用就方便多了!!!
大概實現邏輯就是如此了。

后續做的優化

1、不再使用手動部署dynamicVueObject.vue到一個全局的地方。

不再使用手動部署dynamicVueObject.vue到一個全局的地方,而是在dynamicVueObject.js第一次執行的時候,在body上添加一個動態創建的新div上。

let dyVueObj = Vue.extend({
        /*****dynamicVueObject.vue******/
});
// 添加到一個動態的div上
let instance = new dyVueObj({
    el: document.createElement('div'),
});
// 添加到body上
document.body.appendChild(instance.$el);

export function createVueObj(vueClass, attr, event) {
    let vueObj = Vue.extend(vueClass);
    dynamicVueEvent.emit('addVueObj', vueObj, attr, event);
    return vueObj;
}

export function deleteVueObj(obj) {
    dynamicVueEvent.emit('deleteVueObj', obj);
}

2、支持同時彈出多個組件

allGlobleVue改為了一個數組,支持添加多個彈出組件createVueObj方法的返回值,作為deleteVueObj刪除方法的參數。

let dyVueObj = Vue.extend({
    created() {
        // 支持全局調用臨時添加,用完即刪型vue組件
        dynamicVueEvent.on('addVueObj', (vueObj, attr, event) => {
            this.allGlobleVue.push({
                vueObj, attr, event
            });
        });
        dynamicVueEvent.on('deleteVueObj', (vueObj) => {
            this.allGlobleVue.splice(this.allGlobleVue.findIndex(item => {
                return item.vueObj === vueObj
            }), 1);
        });
    },
    data() {
        return {
            allGlobleVue: [] // 臨時動態添加的窗體,例如彈窗等
        }
    },
    render(createElement) {
        return createElement('div', {}, this.allGlobleVue.map(item => {
            return createElement(item.vueObj, {
                props: item.attr,
                on: item.event
            })
        }));
    }
});

完整代碼的git倉庫是
https://github.com/13601313270/dynamicVueObject
完整的功能,支持同時彈出多個組件,git倉庫里包含一個可以跑起來的demo。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • vue概述 在官方文檔中,有一句話對Vue的定位說的很明確:Vue.js 的核心是一個允許采用簡潔的模板語法來聲明...
    li4065閱讀 7,266評論 0 25
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內容,還有我對于 Vue 1.0 印象不深的內容。關于...
    云之外閱讀 5,074評論 0 29
  • 本文章是我最近在公司的一場內部分享的內容。我有個習慣就是每次分享都會先將要分享的內容寫成文章。所以這個文集也是用來...
    Awey閱讀 9,467評論 4 67
  • 三五元閱讀 232評論 1 1
  • 親子日記第五十八篇 今天開家長會了,孩子爸爸參加的,家長會結束后又接著參加了家委會的會議,所以回到家已經8點20了...
    小丸子兜兜閱讀 181評論 0 0