一:场景
有些时候,我们可能会封装一些(业务层不是通用类)的组件。对于Vue而且
- 业务层组件会在多个组件中使用,这样我们需要在多个组件中引入然后注册。可不可以通过一次注册完成之后,通过激活的方式实现。
- 有的时候我们可能要通过axios拦截一些状态码,比如411或者433,通过不同状态码来实现展示不同的提示框或者弹窗组件。我们不可能再每个页面上都去注册弹窗组件。可不可以通过一个统一的方法去注册,然后调用呢?rcg-component你值得拥有。
先看一下目前实现的效果:
可以在全局任意地方实现组件的挂载。同时当组件mounted的时候,可以监听到component is mounted 当组件卸载的时候, 可以监听到 component is destroyed,同时还会执行一些对应的副作用函数。
二:设计
1: 注册流程
在注册之前,肯定是需要把对应的组件引入(不引入咋能注册呢),在当前的大环境下,组件的引入最多使用的方式是ESM。
import component from 'xx/xx/component'
一般这种引入方式会在.vue文件中使用,但是考虑到,在.vue文件中引入复用性和支持性很差(.vue文件需要额外的loader去解析, 这里希望可以有更好的扩展性),所以选择在js文件中引入。
现在我们在一个vue2.0的项目中引入一下试试看:
新建了一个testComponent.vue,作为注册组件。里面代码很简单:
<template>
<div class="myTest" v-if="show">
{{myTest}}
<button @click="showOn">showOn</button>
</div>
</template>
<script>
export default {
name: 'testComponent',
props: {
},
data() {
return {
myTest: '挂载组件test',
show: true
}
},
methods: {
showOn() {
this.show = false;
}
},
}
</script>
<style scoped>
.myTest {
position: absolute;
top: 50%;
left: 50%;
background: yellow;
}
</style>
之后在main.js中引入:
import testComponent from './components/testComponent.vue';
console.log(testComponent), 看看这个到底是什么玩意
很简单就是一个object里面包含着所有用户自定义设置的options API和reactive Data。这就好办啦,我们是不是可以手动指定一个comonent,然后把这个object挂载上去,完成渲染。
2: 挂载
考虑到我们获取到testComponent是一个包含着vue component options的对象,有没有一种方法,可以把当前的对象转化为一个Vue的子类。然后进行挂载。为什么要转为vue的子类呢?
- 在Vue2.0中,我们是通过new Vue({}),来实现vue的创建,其实每一个vue的component最后都是转化成了Vue的子类,然后挂载到对应的组件下。
把当前的对象转化为vue子类
在Vue官网中的例子:
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
ok。我们在组件中试一下, 在main.js中:
import Vue from 'Vue';
import testComponent from './components/testComponent.vue';
const extend_component = Vue.extend(testComponent);
console.log(extend_component);
是不是觉得很熟?简单点来说这就是Vue组件的构造函数。按照官网中的使用方法:我们new一下:
let re_component = new extend_component();
console.log(re_component);
一个完美的Vue实例出现在我们面前。接下来我们对它进行手动挂载到对应的dom上就可以了。
$mount 挂载
在vue官网中的实例:
var MyComponent = Vue.extend({ template: '<div>Hello!</div>' });
new MyComponent().$mount('#app');
// 或者,在文档之外渲染并且随后挂载
var component = new MyComponent().$mount();
document.getElementById('app').appendChild(component.$el)
只需要简单挂载一下就好:
const mout_component = new re_component().$mount();
document.getElementById('app').appendChild(mout_component.$el);
ok! 之后我们在Vue尝试一下:
import testComponent from './components/testComponent.vue';
const renderComponent = Vue.extend(testComponent);
const mountedComponent = new renderComponent().$mount();
new Vue({
router,
render: h => h(App)
}).$mount('#app')
document.getElementById('app').appendChild(mountedComponent.$el);
这里有个注意点,appendChildren的时候,一定要在你需要挂载点组件已经存在的情况下执行。 在这里是app,所以我们需要在$mount('#app')之后执行。
运行结果:
可以看到组件被append到app下面。
3: 批量处理
这种需要全局挂载点组件可能有很多,我们需要注意几点
- 我们需要设计一个数据结构,可以支持多个挂载。而且最好每一个组件都有自己的类型,类型是唯一的,然后我们通过类型完成挂载。
- 不可能import组件和挂载组件一起执行,所以要耦合开,封装一个单独的方法,提供挂载能力。
批量import
没什么好说的导入多个组件。主要就是设计一个数组,每个数组传入一个对象 {type: 'xxx', component: 'xxx'}
import testAcomponent from '@/component/testAcomponent';
import testBcomponent from '@/component/testBcomponent';
const arrayList = [
{ type: 'yellowUp', component: testAcomponent },
{ type: 'redUp', component: testBcomponent }
]
挂载方法封装
实现init方法:
- 我们想实现只需要全局init一次,就可以在全局任意地方完成调用renderComponent。并且当前组件之间的状态是共享的。所以在init中return一个函数,通过闭包的方式,完成上下文之间的数据共享。 同时,我们希望每个组件只会生成一个实例。这里我们init的时候会创建一个类。
class ExComponent {
constructor(vue, componentsList) {
this.em = vue;
this.componentsList = componentsList;
this.bucketMap = new Map(); // 这里通过map结构可以更方便数据的存取
this.arrayToMap(); // 把传入的array转成map
}
arrayToMap() {
this.componentsList.forEach(item => {
const { type, component } = item;
this.bucketMap.set(type, {component});
})
console.log(this.bucketMap);
}
extendComponent(element) {
// 上文提到的挂载方法
let myBox = this.em.extend(element);
let myComponent = new myBox().$mount();
document.getElementById("app").appendChild(myComponent.$el);
}
}
function init(vue, componentsList) {
const C = new ExComponent(vue, componentsList);
return {
renderComponent: (type) => renderComponent(type, C); // 挂载函数
}
}
ok! 我们在组件中试一下:
可以看到key就是我们传入的类型, component就是待渲染的组件。
之后我们完善一下renderComponent:
function renderComponent(tag, c) {
const ctx = c.bucketMap.get(tag);
c.extendComponent(ctx.component);
}
通过tag,获取对应的组件,然后执行挂载方法。
这只是最简单一个功能,之后会陆续更新rcg-component的实现流程。有兴趣的大哥,给个赞哦!😍