为了顺利使用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()方法进行挂载
xml 复制代码
 // HelloWorld.vue
 <template>
     <div>{{ msg }}</div>
 </template>
 ​
 <script>
 export default {
     data() {
       return {
         msg: 'hello world'
       }
     }
 }
 </script>

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

xml 复制代码
 <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组件,我是这么写的:

ini 复制代码
 const DialogCtor = Vue.extend(Dialog);
 const dialogInstance = new DialogCtor();
 dialogInstance.$mount();
 document.body.appendChild(instance.$el);

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

xml 复制代码
 // 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属性不就好了,我们来试一下。

ini 复制代码
 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?

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

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

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

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

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

改造代码

ini 复制代码
 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监听即可,代码如下:

ini 复制代码
 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吗?欢迎留言讨论!一起学习~

相关推荐
m0_748248771 小时前
【前端 Uniapp】使用Vant打造Uniapp项目(避坑版)
前端·uni-app
深海的鲸同学 luvi1 小时前
高德地图离线加载解决方案(内网部署)+本地地图瓦片加载
前端·javascript·html5
码字哥2 小时前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
GIS好难学4 小时前
《Vue进阶教程》第六课:computed()函数详解(上)
前端·javascript·vue.js
nyf_unknown4 小时前
(css)element中el-select下拉框整体样式修改
前端·css
m0_548514774 小时前
前端打印功能(vue +springboot)
前端·vue.js·spring boot
执键行天涯4 小时前
element-plus中的resetFields()方法
前端·javascript·vue.js
一个努力学习的小男孩4 小时前
【自学】Vues基础
vue.js
Days20504 小时前
uniapp小程序增加加载功能
开发语言·前端·javascript
喵喵酱仔__4 小时前
vue 给div增加title属性
前端·javascript·vue.js