vue2與vue3中通過(guò)函數(shù)方式調(diào)用全局組件

框架:vue2.x、vue3
ui框架:element-ui、element-plus

在開(kāi)發(fā)頁(yè)面時(shí),經(jīng)常會(huì)遇到類(lèi)似的需求,某某情況下彈個(gè)窗提示,達(dá)到引導(dǎo)用戶(hù)的效果,比如沒(méi)有權(quán)限,提示用戶(hù)去充錢(qián),比如下面:


image.png

這種情況下的彈窗除了提示功能外沒(méi)有其他任務(wù)的交互動(dòng)作,而我們?cè)诮M件中寫(xiě)一個(gè)彈窗得這樣:

template:
    <el-dialog
      custom-class="edition-dialog"
      :visible.sync="dialogVisible"
      width="560px"
      @close="close">
      <div class="content">
        <div class="title">{{title}}</div>
        <div class="text-content">{{content}}</div>
        <div class="btn-group">
          <div class="btn sure" @click="close">確認(rèn)</div>
        </div>
      </div>
    </el-dialog>

js:
data() {
  return {
    dialogVisible: false
  }
},
methods: {
  open() {
    this.dialogVisible = true
  },
  close() {
    this.dialogVisible = false
  }
}

可以看到需要一大段的html來(lái)定義,還要控制顯示隱藏的變量,顯示隱藏的方法。需要非常多的代碼片段來(lái)支持,會(huì)使我們的組件變得非常雜亂,只為了一個(gè)提示彈窗,而且既然是項(xiàng)目的提示彈窗,那么基本在很多地方都會(huì)用到,這樣一個(gè)組件一個(gè)組件去寫(xiě)肯定不是辦法。

解決方法之一就是定義個(gè)全局組件
彈窗的標(biāo)題,內(nèi)容由props傳入,但還是要由外部去控制它的顯示隱藏,使用情況會(huì)變成類(lèi)似下面的情況:

調(diào)用的地方:

template:
<dialog-com title="xxx" content="xxxx" :visible.sync="dialogVisible">
</dialog-com>

js:
data() {
  retrun {
    dialogVisible: false
  }
},
methods: {
  open() {
    this.dialogVisible = true
  }
}

彈窗的關(guān)閉需要在組件內(nèi)去改變父組件的變量,就是上面?zhèn)魅?strong>visible后面的.sync修飾符,在彈窗組件中就可以:

dialog-com組件:

props: {
  visible: {
    type: Boolean
  }
},
methods: {
  close() {
    this.$emit('update:visible', false) // 這樣就可以將父組件中的dialogVisible變成false
  }
}

這樣在調(diào)用彈窗的地方還是要定義變量與方法,而且要在template中寫(xiě)好標(biāo)簽,還是顯得麻煩,所以想要實(shí)現(xiàn)的就是函數(shù)調(diào)用的方式,比如通過(guò)this.$dialog()的方式調(diào)用,像調(diào)用elementmessage組件一樣方便。

接下來(lái)就是實(shí)現(xiàn),進(jìn)入主題。

vue2中實(shí)現(xiàn)
彈窗的顯示與隱藏通過(guò)掛載到body上面與從body上移除來(lái)實(shí)現(xiàn),下面是我的組件目錄:

image.png

index.vue中編寫(xiě)組件,在index.js中實(shí)現(xiàn)函數(shù)調(diào)用,先看index.vue:

<template>
  <div class="edition-wrapper">
    <el-dialog
      custom-class="edition-dialog"
      :visible.sync="dialogVisible"
      width="560px"
      @close="close">
      <div class="content">
        <div class="title">{{title}}</div>
        <div class="text-content">{{content}}</div>
        <div class="btn-group">
          <div class="btn sure" @click="close">確認(rèn)</div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dialogVisible: true,
      title: '',
      content: ''
    }
  },
  methods: {
    close() {
      this.$el.parentNode.removeChild(this.$el);
    }
  }
}
</script>

樣式就不貼出來(lái)了,就是一個(gè)普通的組件,當(dāng)關(guān)閉彈窗時(shí)觸發(fā)close方法,在這個(gè)方法中移除這個(gè)組件,this.$el拿到組件渲染后的dom,然后就是原生的js api了。

接下來(lái)就重要的index.js:

import Vue from 'vue'
import EditionTip from './index.vue'

let EditionTipConstructor = Vue.extend(EditionTip)
let instance

const editionTip = function(options = {}) {
  instance = new EditionTipConstructor({
    data: options
  })
  document.body.appendChild(instance.$mount().$el)
}

export default editionTip

里面最重要的就是Vue.extend,通過(guò)它來(lái)創(chuàng)建一個(gè)Vue的子類(lèi),傳入一個(gè)組件對(duì)象,然后通過(guò)new來(lái)創(chuàng)建組件實(shí)例,new的時(shí)候可以傳入組件的屬性,比如data、methods生命周期函數(shù)等等,它會(huì)和組件內(nèi)定義的屬性結(jié)合,而不會(huì)直接覆蓋該屬性。
然后通過(guò).$mount()來(lái)渲染組件,上面的代碼中,當(dāng)執(zhí)行instance.$mount()時(shí)會(huì)渲染組件,但還沒(méi)掛載到頁(yè)面上,再通過(guò).$el就拿到組件實(shí)際上的dom了,當(dāng)然$mount()也可以掛載,可以參考你們項(xiàng)目中的main.js,所以上面的掛載到body上面也可以寫(xiě)成:

instance.$mount('body')

到這里就很清晰了,通過(guò)調(diào)用editionTip函數(shù),來(lái)創(chuàng)建一個(gè)彈窗組件掛載到body上面,我們可以掛載到Vueprototype上,方便調(diào)用,在mian.js中:

import EditionTip from '@/common/editionTip/index.js' // 路徑
Vue.prototype.$EditionTip = EditionTip

然后在組件中:

this.$EditionTip({
  title: '接碼價(jià)格波動(dòng)趨勢(shì)',
  content: 'KARMA基礎(chǔ)版用戶(hù)無(wú)法查看產(chǎn)品相關(guān)的價(jià)格波動(dòng)趨勢(shì)情況,如需體驗(yàn)該專(zhuān)業(yè)版功能請(qǐng)與您的責(zé)任銷(xiāo)售聯(lián)系,或者致電400-809-3699由我們?yōu)槟峙洹?
})

參數(shù)就是上面的options,會(huì)和組件里面的data結(jié)合。

vue3中實(shí)現(xiàn)
vue3一來(lái),好家伙,全都變了,官方文檔又只能看懂一丟丟,想實(shí)現(xiàn)某個(gè)功能發(fā)現(xiàn)和vue2的不一樣就頭疼了,在現(xiàn)階段搜索還真的搜不到。下面的實(shí)現(xiàn)方法純屬我自己瞎折騰出來(lái)的,如果你有更好的實(shí)現(xiàn)方法,求指教。
vue3中創(chuàng)建根實(shí)例不再是new Vue了,而是通過(guò)createApp來(lái)創(chuàng)建,但和vue2一樣的是都是掛載到頁(yè)面某個(gè)dom上,這里貼一下vue3中的main.ts(我用的ts,下面代碼與用js無(wú)實(shí)質(zhì)區(qū)別)

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'

createApp(App).use(ElementPlus).mount('#app')

首先掛載組件是用過(guò)createAppmount來(lái)的,那取消掛載呢,在官方文檔發(fā)現(xiàn)了createApp出來(lái)的實(shí)例有一個(gè)unmount方法,可以來(lái)在頁(yè)面中銷(xiāo)毀組件的dom。
然后我們要可以傳遞變量給組件,那通過(guò)createApp()這種方法怎么傳遞變量給組件呢,我們點(diǎn)進(jìn)去createApp這個(gè)方法里面去看看:

image.png

發(fā)現(xiàn)第二個(gè)參數(shù)可以傳遞props,那就可以解決我們傳遞變量這個(gè)需求,而且還解決了下一個(gè)需求。
上面說(shuō)了我們銷(xiāo)毀組件要通過(guò)調(diào)用createApp出來(lái)的實(shí)例的unmount方法,但是我們是將組件傳遞給createApp這個(gè)方法,所以在組件里面是操作不了createApp出來(lái)的實(shí)例的,就需要有外部傳遞一個(gè)方法給組件供組件去調(diào)用銷(xiāo)毀自己(我殺我自己?)
大概思路就是如此,接下來(lái)是實(shí)現(xiàn),與vue2中一樣,兩個(gè)文件一個(gè)寫(xiě)組件一個(gè)寫(xiě)掛載的方法:
image.png

首先dialog.vue:

<template>
  <el-dialog
    :title="title"
    v-model="dialogVisible"
    width="30%"
    @close="close">
    <span>{{content}}</span>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="close">取 消</el-button>
        <el-button type="primary" @click="close">確 定</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  props: {
    close: {
      type: Function
    },
    content: {
      type: String,
      default: '暫無(wú)權(quán)限'
    },
    title: {
      type: String,
      default: '提示'
    }
  },
  setup () {
    return {
      dialogVisible: true
    }
  }
})
</script>

vue3template可以不用根標(biāo)簽。關(guān)閉彈窗的時(shí)候調(diào)用props傳進(jìn)來(lái)的close方法。

dialog.ts:

import { createApp } from 'vue'
import dialog from './dialog.vue'

interface Option{
  title?: string;
  content?: string;
}

function mountContent (option = {} as Option) {
  const app = createApp(dialog, {
    close: () => { app.unmount('body')},
    ...option
  })
  app.mount('body')
}
export default mountContent

這是最基礎(chǔ)的思路,掛載到body上面。我們?cè)谀硞€(gè)組件中引入這個(gè)方法:

<template>
  <div class="home">
    <el-button type="primary" @click="showDialog">顯示提示彈窗</el-button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import mountDialog from '../components/dialog'
export default defineComponent({
  setup () {
    const showDialog = () => {
      mountDialog({ title: '自定義標(biāo)題', content: '自定義內(nèi)容' })
    }

    return {
      showDialog
    }
  }
})
</script>

image.png

點(diǎn)擊后:
image.png

我們發(fā)現(xiàn)整個(gè)body下面的東西全部被我替換成組件里面的dom,而且element的組件也沒(méi)渲染
解決第一個(gè)問(wèn)題很簡(jiǎn)單,我們創(chuàng)建一個(gè)dom插入body中,再將組件掛載到這個(gè)dom上,這樣就不會(huì)印象到其他組件。
第二個(gè)問(wèn)題是因?yàn)槲覀冞@個(gè)app實(shí)例和mian中的app實(shí)例是完全不同的兩個(gè)app實(shí)例,所以在這邊也需要use一下element
改版后的dialog.ts:

import { createApp } from 'vue'
import dialog from './dialog.vue'
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'

interface Option{
  title?: string;
  content?: string;
}

function mountContent (option = {} as Option) {
  const dom = document.createElement('div')
  document.body.appendChild(dom)
  const app = createApp(dialog, {
    close: () => { app.unmount(dom); document.body.removeChild(dom) },
    ...option
  })
  app.use(ElementPlus).mount(dom)
}
export default mountContent

調(diào)整之后:

image.png

完美。
vue3我也是剛接觸,如果有更正確的實(shí)現(xiàn)方式歡迎指教哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容