美文网首页
vue2与vue3中通过函数方式调用全局组件

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

作者: 超人鸭 | 来源:发表于2020-12-24 18:41 被阅读0次

    框架: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我也是刚接触,如果有更正确的实现方式欢迎指教哦。

    相关文章

      网友评论

          本文标题:vue2与vue3中通过函数方式调用全局组件

          本文链接:https://www.haomeiwen.com/subject/wzzmnktx.html