Skip to content
On this page

全局组件注册


全局组件注册

上篇我们介绍了如何去写一个全局组件,接下来,我们需要了解一下,全局组件如何被模板使用呢? 把代码渲染出来有两个方案

  1. 通过注册组件 的方式,把代码注册为 vue 实例的组件,注册组件又分 全局注册局部注册 两种方式
  2. 通过挂载点直接挂载vue实例, 即通过 new Vue({ el: '#id' }) 的方式

注册组件

要想注册组件,vue 提供了2中方式来注册组件,先来看看全局注册:

javascript
Vue.component('my-component-name', {
  // ... 选项 ...
})

这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例(new Vue)的模板中。但是需要注意的点是:

记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。

而我们的全局组件以及和模板解耦了,运行时 Vue 实例已经创建完,所以直接通过 Vue.component 的方式可能行不通,那么我们可能需要通过局部注册方式来使用全局组件,根据 Vue 官网的表述,注册局部组件可以这样:

javascript
var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

但是问题在于这样我们就必须提前知道组件的名称,所以有没有其他的方式?是否想到了前面我们再介绍模板组件的时候,提到了动态组件,这里是否可以通过动态组件来实现动态化呢?我们尝试改成这样:

javascript
<template>
  <div
    v-if="component"
    :is="component"
    :obj="props"
  />
</template>

export default {
  data() {
    return {
      component: '',
    }
  },
  
 created() {
   this.component = 'component-a';
 }
}

这样看起来是可以让组件动态的渲染出来,但是还存在一个问题 component-a 我们是个字符串,并没有描述是个啥玩意,所以我们可能需要动态注册 component-a。除了通过 Vue.component 的方式,我们可以使用 Vue-extend 来创建一个子类,这样就可以构造一个 Vue 组件:

javascript
this.compoent = Vue.extend({
  template: '',
  data: () => {}
});

再结合我们之前说的,我们的组件以及被编译好了静态的 js umd 和 css 所以,我们只需要把 js 和 css 动态的挂在到 DOM 上,再由 DOM 渲染加载。最后通过 window 的形式加载:

javascript
export default {
   // ...
   created() {
    // 动态添加组件,用于可视化编辑场景
    const {
      name,
      js,
      css,
      index,
    } = this.config;
    const component = window[name];
    if (!component) {
      const script = document.createElement('script');
      const link = document.createElement('link');
      script.src = js;
      link.href = css;
      link.rel= 'stylesheet';
      // 动态注入 js 和 css
      document.head.appendChild(link);
      document.body.appendChild(script);
      script.onload = () => {
        // 加载完成
        this.$emit('onRemoteComponentLoad', {
          ...window[name],
          index,
        });
        this.component = Vue.extend(window[name].Component);
      }
    } else  {
      // 非动态化添加,用于server构建场景
      this.$emit('onRemoteComponentLoad', {
        ...window[name],
        index,
      });
      // 先有 props 再挂组件,不然 props 是 null 可能会有错
      this.$nextTick(() => {
        this.component = Vue.extend(window[name].Component);
      });
    }
  },
}

既然我们的模板开发和组件开发已经解耦,有模板来动态加载渲染远程 js,如果全局组件内部运行出现了错误。那这部分的处理需要通过在容器组件上添加 errorCaptured 这个官方钩子,来捕获子组件的错误:

javascript
errorCaptured(err, vm, info) {
  // todo
},

你可以在此钩子中修改组件的状态。因此在捕获错误时,在模板或渲染函数中有一个条件判断来绕过其它内容就很重要;不然该组件可能会进入一个无限的渲染循环。

动态实例

这种方式主要就是利用 Vue 可以动态的实例化到一个节点上 new Vue(component).$mount(component.$el)这种方式。我们看一下官方的介绍:

如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素,并且你必须使用原生 DOM API 把它插入文档中。

所以这种形式的渲染,就必须要求我们提前感知组件需要插入到模板的具体 DOM 节点,无疑带来了开发成本。所以我们可以放弃这种方案,不做过多介绍,有兴趣的小伙伴可以自行尝试,如果有突破性进展,也可以互相交流学习,当然除了这些还有一些其他的弊端,比如:

  1. 需要一个稳定的挂载点
  2. 首次挂载需要等 DOM 流渲染完成后才能实例化

总结

本章我们主要介绍了远程组件的注册方式,回顾一下,我们通过动态注入 js 和 css 将组件的信息注册到了 window 上,然后再从 window中获取组件的信息来注册组件,最后通过动态组件的方式来挂在到页面。

有了这些知识后,我们再来思考之前模板章节提到的一个问题:当组件更新后,如何确保组件更新的影响面可控,也就是组件的更新策略我们该如何设计呢?