为了顺利使用Vue.extend,我被迫去研究了Vue源码...

前言

大家好,我是小寒!

事情是这样的,我们在开发PC端项目的时候,封装了一个Dialog组件,我希望这个组件能像Element UI里面的Message组件一样,通过this.$message.success()这样的类似API方式去调用,这时候就需要Vue.extend出场了。

Vue.extend介绍

先来看一下Vue官方文档对它的描述:

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

组件选项指的是我们平时书写.vue文件的script标签中export default的那个对象,把这个对象传入Vue.extend后会返回一个子类的构造器。

Vue.extend使用

它的使用分为3步:

  • 调用Vue.extend创建一个组件的构造器Ctor
  • 通过new Ctor创建一个组件实例instance
  • 调用instance.$mount()方法进行挂载
// HelloWorld.vue
<template>
    <div>{{ msg }}</div>
</template>

<script>
export default {
    data() {
      return {
        msg: 'hello world'
      }
    }
}
</script>

使用Vue.extend手动挂载HelloWorld组件。

<template>
  <div id="app">
    app <br/>
    <button @click="mountComponent">点击挂载组件</button>
  </div>
</template>

<script>
import HelloWorld from '@/components/HelloWorld.vue';
import Vue from "vue";

export default {
  name: "App",
  components: {
    HelloWorld
  },
  methods: {
    mountComponent() {
      const Ctor = Vue.extend(HelloWorld);
      const instance = new Ctor();
      // 挂载方式1:直接调用$mount方法挂载,但目标节点会被清空
      instance.$mount('#app');
      // 挂载方式2:先调用$mount进行空挂载,然后使用DOM API直接将组件DOM插入到页面上
      // instance.$mount();
      // document.body.appendChild(instance.$el);
    }
  },
}
</script>

代码中展现了两种挂载方式,方式1是直接挂载到某个目标DOM上,值得注意的是,这个目标DOM里面原来如果有内容,会被清空,而方式2则不会,纯粹就是一个DOM操作,很好理解。

挂载Dialog组件

那接下来我就通过vue.extend去挂载Dialog组件,我是这么写的:

const DialogCtor = Vue.extend(Dialog);
const dialogInstance = new DialogCtor();
dialogInstance.$mount();
document.body.appendChild(instance.$el);

接下来就遇到问题了,因为执行完这段代码后,界面毫无反应。下面是Dialog.vue的代码,我这里简化了业务代码只放了一个demo

// Dialog.vue
<template>
   <el-dialog
    title="提示"
    :visible.sync="visible"
    width="30%">
    <span>这是一段信息</span>
    <span slot="footer" class="dialog-footer">
        <el-button @click="onClose">取 消</el-button>
        <el-button type="primary" @click="onConfirm">确 定</el-button>
    </span>
    </el-dialog>
</template>

<script>
export default {
    props: {
        visible: {
            type: Boolean,
            default: false
        },
        options: {
            type: Object,
            default: () => ({})
        },
        // ...
    },
    methods: {
        onConfirm() {
            this.$emit('update:visible', false);
        },
        onClose() {
            this.$emit('update:visible', false);
        }
        // ...
    }
}
</script>

为何界面没渲染出Dialog?

原因显而易见,Dialog组件的显示隐藏是由props中的visible来控制的,我挂载的时候根本就没传入props,所以界面就没渲染出来。

既然如此,那我们在创建的时候传入一个visible属性不就好了,我们来试一下。

const DialogCtor = Vue.extend(Dialog);
const dialogInstance = new DialogCtor({
  props: {
    // 这里创建实施的时候传入 visible 的值
    visible: true,
  }
});
dialogInstance.$mount();
document.body.appendChild(instance.$el);

然后发现,还是一点效果没有。

在仔细思考了一下,这里其实要解决的有两个问题:

  1. 如何传递props?
  2. 如何监听Dialog组件内部emit出来的事件?

为了解决这两个问题,我特意去找了Vue官方文档,发现并没有提及props,网上找了一圈也没有找到解决方案,既然如此,求人不如求己,那我自己去Vue源码中找找线索吧。

问题1:如何传递props?

img

我看了一下Vue.extend、以及组件的初始化流程相关源码,发现从Vue.extend这个流程去创建组件时,在初始化props时,会直接到$options.propsData中取数据。还记得我们创建组件instance的时候,传入了一个对象,new DialogCtor(obj),这个obj就是$options的一部分。

所以我们传递属性的时候,不应该传props,而应该传propsData

问题2:如何监听Dialog组件内部emit出来的事件?

接着来看初始化事件的源码部分。

img

这里取了$options._parentListeners的值去初始化事件,所以同样我们需要通过_parentListeners来传递事件。

改造代码

const DialogCtor = Vue.extend(Dialog);
const dialogInstance = new DialogCtor({
  // 通过propsData传递值
  propsData: {
    visible: true,
  },
  _parentListeners: {
  'update:visible'(val)  {
    // 修改visible的值
    dialogInstance._props.visible = val;
  }
});
dialogInstance.$mount();
document.body.appendChild(instance.$el);

这里我在update:visible的监听回调中,修改的是dialogInstance._props中的visible的值,其实你直接修改dialogInstance.visible = val也是没问题的,最终也是修改的dialogInstance._props的值。

监听事件这里,后面我还发现还有另一种写法,直接用$on监听即可,代码如下:

const DialogCtor = Vue.extend(Dialog);
const dialogInstance = new DialogCtor({
  // 通过propsData传递props
  propsData: {
    visible: true,
  },
});
// 用 $on 监听事件
dialogInstance.$on('update:visible', (val) => {
  dialogInstance.visible = val;
})
dialogInstance.$mount();
document.body.appendChild(dialogInstance.$el);

这样就能在不修改Dialog里面业务代码的情况下,使用Vue.extend成功实现组件了。

小结

上面介绍了Vue.extend在业务中的一个使用,在遇到问题的时候查阅了vue源码,从源码中找到一些思路并成功解决了问题,也是对Vue.extend有了一些深入理解,希望我的经验能帮助到大家!

大家在平时开发中有用过Vue.extend吗?欢迎留言讨论!一起学习~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值