likes
comments
collection
share

Vue独立组件开发: 函数调用组件——Vue.extend及$mount

作者站长头像
站长
· 阅读数 14

本文正在参加「金石计划」

本文是介绍独立组件开发的系列文章的第三篇。

第一篇文章: Vue独立组件开发: prop event slot

第二篇文章:Vue 独立组件开发:不一样的组件通信方式

应用场景

vue项目中,组件一般都是静态的写在template标签里面,比如:

<template>
    <div>
        <my-component></my-component>
    </div>
</template>

渲染时,自定义标签my-component就会被替换为组件的内容,在哪写的组件标签,就会在哪里被替换。

换句话说,常规的组件使用方式,只能在规定的地方渲染组件。但是,在下面的两种场景中就比较局限了:

场景一:实现类似原生 window.alert() 的提示框组件,它的位置是在 <body> 下,而非 <div id="app">,并且不会通过常规的组件自定义标签的形式使用,而是像 JS 调用函数一样使用;

场景二:组件的模板是通过调用接口从服务端获取的,需要动态渲染组件;

一般来说,在我们访问页面时,组件就已经渲染就位了,对于场景一,其实并不陌生,在 jQuery 时代,通过操作 DOM,很容易就能实现,当然你也可以这么做,但是这不是 Vue 推荐的做法,既然使用 Vue 了,就应该用 Vue 的思路来解决问题。对于场景二,组件的渲染是异步的,预先不知道模板是什么。

因此,对于这两种场景,Vue.extendvm.$mount 语法就派上用场了。

Vue.extendvm.$mount的用法

Vue.extend 的作用,就是基于 Vue 构造器,创建一个 子类构造器VueComponent,我们看下它的源码:

// Vue的构造函数
function Vue() {
    this._init(options)
}

// Vue.extend创建组件的构造函数,它继承于Vue
Vue.extend = function (extendOptions) {
  ...
  const Super = Vue
  // 组件构造函数
  const Sub = function VueComponent (options) {
    this._init(options)
  }
  // 组件构造函数的原型指向Vue的原型,实现继承
  Sub.prototype = Object.create(Super.prototype)
  // 把组件构造函数的constructor重新指向组件构造函数
  Sub.prototype.constructor = Sub
  ...
  return Sub
}

从源码中,可以非常看清楚子类的构造函数VueComponent继承于父类Vue

Vue中,组件并不是Vue实例,组件的构造是VueComponent,两者并不相同,而是一个继承关系,切记切记。

我们首先看一下 Vue 实例的挂载过程:首先通过new Vue实例化一个Vue实例,实例上有个$mount方法,调用$mount方法,传入要挂载的dom节点,进行挂载。

var app = new Vue({ 
    render: h => h(App) 
}).$mount('#app')

同样地,组件的创建过程也是一样的。它的参数跟 new Vue 的基本一样,但 data 要跟组件一样,是个函数,再配合 $mount ,就可以让组件渲染,并且挂载到任意指定的节点上,比如 body

针对上面的场景,就可以这样写:

import Vue from 'vue'
const ConfirmComponent = Vue.extend({ 
    template: '<div>{{ message }}</div>', 
    data () { 
        return { 
            message: 'Hello, vue' 
        }; 
    }
});

这一步创建了一个构造器,这个过程也可以解决异步获取 template 模板的问题,下面要手动渲染组件,并把它挂载到 body 下:

const component = new ConfirmComponent().$mount();

这一步,我们调用了 $mount 方法对组件进行了手动渲染,但它仅仅是被渲染好了,也就是把组件内容转化为了真实的DOM 节点,但并没有挂载到节点上,也就显示不了组件。此时的 component 已经是一个标准的 Vue 组件实例,因此它的 $el 属性也可以被访问,也就是组件对应的真实DOM赋值给了$el属性。

最后,通过appendChild方法挂载到body上。当然,除了 body,你还可以挂载到其它节点上。

document.body.appendChild(component.$el);

$mount 也有一些快捷的挂载方式,以下两种都是可以的:

// 在 $mount 里写参数来指定挂载的节点 
new ConfirmComponent().$mount('#app'); 
// 不用 $mount,直接在创建实例时指定 el 选项 
new ConfirmComponent({ el: '#app' });

实现同样的效果,除了用 Vue.extend 外,也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件:

import Vue from 'vue';
import Notification from './notification.vue'; 

const props = {};  // 这里可以传入一些组件的 props 选项

const Instance = new Vue({
  render (h) {
    return h(Notification, {
      props: props
    });
  }
});

// 生成真实DOM
const component = Instance.$mount();
// 把真实DOM挂载到body节点上
document.body.appendChild(component.$el);

这样既可以使用 .vue 来写复杂的组件(毕竟在 template 里堆字符串很痛苦),还可以根据需要传入适当的 props

渲染后,如果想操作 Render 的 Notification 实例,也是很简单的,只需要找到Notification 实例即可。

因为我们是通过Vue来实例化的,内部再通过VueComponent来实例化了一个组件,两者就构成了父子关系,所以可以这么获取子组件的实例:

const notification = Instance.$children[0];

需要注意的是,我们是用 $mount 手动渲染的组件,如果要销毁,也要用 $destroy 来手动销毁实例,必要时,也可以用 removeChild 把节点从 DOM 中移除。

总结

这两个 API 如果你不对 Vue 源码有一定的了解,你可能并不能真正意义上的弄懂上面的写法。所以,我个人还是非常适当的阅读下Vue源码。

在多数情况下,我们只关注在业务层,并使用现成的组件库,所以使用这两个API的情况较少。

转载自:https://juejin.cn/post/7222921573622120507
评论
请登录