为了顺利使用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吗?欢迎留言讨论!一起学习~

相关推荐
Apifox11 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
-代号952716 分钟前
【JavaScript】十四、轮播图
javascript·css·css3
麦麦大数据34 分钟前
neo4j+django+deepseek知识图谱学习系统对接前后端分离前端vue
vue.js·django·知识图谱·neo4j·deepseek·在线学习系统
树上有只程序猿39 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187301 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox