全局组件的注册与通信 -- rcg-component

316 阅读4分钟

一:场景

有些时候,我们可能会封装一些(业务层不是通用类)的组件。对于Vue而且

  1. 业务层组件会在多个组件中使用,这样我们需要在多个组件中引入然后注册。可不可以通过一次注册完成之后,通过激活的方式实现。
  2. 有的时候我们可能要通过axios拦截一些状态码,比如411或者433,通过不同状态码来实现展示不同的提示框或者弹窗组件。我们不可能再每个页面上都去注册弹窗组件。可不可以通过一个统一的方法去注册,然后调用呢?rcg-component你值得拥有。

先看一下目前实现的效果:

20220630182424.gif

可以在全局任意地方实现组件的挂载。同时当组件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), 看看这个到底是什么玩意

image.png

很简单就是一个object里面包含着所有用户自定义设置的options API和reactive Data。这就好办啦,我们是不是可以手动指定一个comonent,然后把这个object挂载上去,完成渲染。

2: 挂载

考虑到我们获取到testComponent是一个包含着vue component options的对象,有没有一种方法,可以把当前的对象转化为一个Vue的子类。然后进行挂载。为什么要转为vue的子类呢?

  • 在Vue2.0中,我们是通过new Vue({}),来实现vue的创建,其实每一个vue的component最后都是转化成了Vue的子类,然后挂载到对应的组件下。

把当前的对象转化为vue子类

image.png

在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);

image.png

是不是觉得很熟?简单点来说这就是Vue组件的构造函数。按照官网中的使用方法:我们new一下:

    let re_component = new extend_component();
    console.log(re_component);

image.png

一个完美的Vue实例出现在我们面前。接下来我们对它进行手动挂载到对应的dom上就可以了。

$mount 挂载

image.png

在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')之后执行。

运行结果:

image.png

可以看到组件被append到app下面。

3: 批量处理

这种需要全局挂载点组件可能有很多,我们需要注意几点

  1. 我们需要设计一个数据结构,可以支持多个挂载。而且最好每一个组件都有自己的类型,类型是唯一的,然后我们通过类型完成挂载。
  2. 不可能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方法:

  1. 我们想实现只需要全局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! 我们在组件中试一下:

image.png

可以看到key就是我们传入的类型, component就是待渲染的组件。

之后我们完善一下renderComponent:

    function renderComponent(tag, c) {
         const ctx = c.bucketMap.get(tag);
         c.extendComponent(ctx.component);
    }

通过tag,获取对应的组件,然后执行挂载方法。

这只是最简单一个功能,之后会陆续更新rcg-component的实现流程。有兴趣的大哥,给个赞哦!😍