vue2与vue3中通过函数方式调用全局组件

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

在开发页面时,经常会遇到类似的需求,某某情况下弹个窗提示,达到引导用户的效果,比如没有权限,提示用户去充钱,比如下面:


image.png

这种情况下的弹窗除了提示功能外没有其他任务的交互动作,而我们在组件中写一个弹窗得这样:

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">确认</div>
        </div>
      </div>
    </el-dialog>

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

可以看到需要一大段的html来定义,还要控制显示隐藏的变量,显示隐藏的方法。需要非常多的代码片段来支持,会使我们的组件变得非常杂乱,只为了一个提示弹窗,而且既然是项目的提示弹窗,那么基本在很多地方都会用到,这样一个组件一个组件去写肯定不是办法。

解决方法之一就是定义个全局组件
弹窗的标题,内容由props传入,但还是要由外部去控制它的显示隐藏,使用情况会变成类似下面的情况:

调用的地方:

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

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

弹窗的关闭需要在组件内去改变父组件的变量,就是上面传入visible后面的.sync修饰符,在弹窗组件中就可以:

dialog-com组件:

props: {
  visible: {
    type: Boolean
  }
},
methods: {
  close() {
    this.$emit('update:visible', false) // 这样就可以将父组件中的dialogVisible变成false
  }
}

这样在调用弹窗的地方还是要定义变量与方法,而且要在template中写好标签,还是显得麻烦,所以想要实现的就是函数调用的方式,比如通过this.$dialog()的方式调用,像调用elementmessage组件一样方便。

接下来就是实现,进入主题。

vue2中实现
弹窗的显示与隐藏通过挂载到body上面与从body上移除来实现,下面是我的组件目录:

image.png

index.vue中编写组件,在index.js中实现函数调用,先看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">确认</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>

样式就不贴出来了,就是一个普通的组件,当关闭弹窗时触发close方法,在这个方法中移除这个组件,this.$el拿到组件渲染后的dom,然后就是原生的js api了。

接下来就重要的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,通过它来创建一个Vue的子类,传入一个组件对象,然后通过new来创建组件实例,new的时候可以传入组件的属性,比如datamethods生命周期函数等等,它会和组件内定义的属性结合,而不会直接覆盖该属性。
然后通过.$mount()来渲染组件,上面的代码中,当执行instance.$mount()时会渲染组件,但还没挂载到页面上,再通过.$el就拿到组件实际上的dom了,当然$mount()也可以挂载,可以参考你们项目中的main.js,所以上面的挂载到body上面也可以写成:

instance.$mount('body')

到这里就很清晰了,通过调用editionTip函数,来创建一个弹窗组件挂载到body上面,我们可以挂载到Vueprototype上,方便调用,在mian.js中:

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

然后在组件中:

this.$EditionTip({
  title: '接码价格波动趋势',
  content: 'KARMA基础版用户无法查看产品相关的价格波动趋势情况,如需体验该专业版功能请与您的责任销售联系,或者致电400-809-3699由我们为您分配。'
})

参数就是上面的options,会和组件里面的data结合。

vue3中实现
vue3一来,好家伙,全都变了,官方文档又只能看懂一丢丢,想实现某个功能发现和vue2的不一样就头疼了,在现阶段搜索还真的搜不到。下面的实现方法纯属我自己瞎折腾出来的,如果你有更好的实现方法,求指教。
vue3中创建根实例不再是new Vue了,而是通过createApp来创建,但和vue2一样的是都是挂载到页面某个dom上,这里贴一下vue3中的main.ts(我用的ts,下面代码与用js无实质区别)

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')

首先挂载组件是用过createAppmount来的,那取消挂载呢,在官方文档发现了createApp出来的实例有一个unmount方法,可以来在页面中销毁组件的dom
然后我们要可以传递变量给组件,那通过createApp()这种方法怎么传递变量给组件呢,我们点进去createApp这个方法里面去看看:

image.png

发现第二个参数可以传递props,那就可以解决我们传递变量这个需求,而且还解决了下一个需求。
上面说了我们销毁组件要通过调用createApp出来的实例的unmount方法,但是我们是将组件传递给createApp这个方法,所以在组件里面是操作不了createApp出来的实例的,就需要有外部传递一个方法给组件供组件去调用销毁自己(我杀我自己?)
大概思路就是如此,接下来是实现,与vue2中一样,两个文件一个写组件一个写挂载的方法:
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: '暂无权限'
    },
    title: {
      type: String,
      default: '提示'
    }
  },
  setup () {
    return {
      dialogVisible: true
    }
  }
})
</script>

vue3template可以不用根标签。关闭弹窗的时候调用props传进来的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

这是最基础的思路,挂载到body上面。我们在某个组件中引入这个方法:

<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: '自定义标题', content: '自定义内容' })
    }

    return {
      showDialog
    }
  }
})
</script>

image.png

点击后:
image.png

我们发现整个body下面的东西全部被我替换成组件里面的dom,而且element的组件也没渲染
解决第一个问题很简单,我们创建一个dom插入body中,再将组件挂载到这个dom上,这样就不会印象到其他组件。
第二个问题是因为我们这个app实例和mian中的app实例是完全不同的两个app实例,所以在这边也需要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

调整之后:

image.png

完美。
vue3我也是刚接触,如果有更正确的实现方式欢迎指教哦。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容