每个VUE的单文件组件都是一个对象,当多个对象相似时会造成代码的重复。由于面向对象编程思想,我们首先会想到继承,接下来我们探究一下VUE的单文件组件之间的继承。

新近开始了一个项目,用VUE实现组件化大屏设计。大概需求是:让用户可以使用拖拽的方式去设计一个自己想要的大屏展示界面。

这个需求要求大屏的每一个元素都是一个可拖拽添加到画布上的组件,并且组件之间可以任意的互相传递数据进行条件筛选。也就是说,每个组件都包含拖拽相关的方法、初始化之后的位置移动方法以及对外提供筛选条件的方法等。如果正常的组件使用方法,会造成大量代码重复,不利于维护。首先想到的是利用面向对象之继承来避免代码重复,在vue官网上发现vue也提供了extends和mixins的方法来实现组件之间的继承/混入。

根据官网介绍,extends和mixins的选项合并逻辑相同。

官网原文:
数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子**之前**调用。
值为对象的选项,例如`methods`、`components`和`directives`,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

总结来说,VUE合并逻辑将选项分为三类,data、钩子函数、值为对象的选项

  1. data:递归合并,如有冲突,组件数据优先;
  2. 钩子函数都会调用,组件自身钩子后执行;
  3. 值为对象的选项,只会进行第一层合并,如有冲突,组件数据优先。

本着只有自己实现过才有发言权的原则,自己用代码验证了一遍,下面把测试代码拿出来。

子组件

<template>
  <div class="aa">Children1</div>
</template>

<script>
import Base from "./Base";
export default {
  name: "Children1",
  extends: Base,
  data() {
    return {
      // render: 'Children1 Render'
      aa: 'Children1 Render'
    };
  },
  beforeCreate() {
    console.log("==============Children1 beforeCreate start==================");
    console.log(this.render);
    console.log("==============Children1 beforeCreate end==================");
  },
  created() {
    console.log("==============Children1 created start==================");
    console.log(this.$el);
    console.log(this.render);
    console.log("==============Children1 created end==================");
  },
  beforeMount() {
    console.log("==============Children1 beforeMount start==================");
    console.log(this.$el);
    console.log("==============Children1 beforeMount end==================");
  },
  mounted() {
    console.log("==============Children1 mounted start==================");
    console.log(this.$options.extends.$el);
    console.log(this.$el);
    console.log("==============Children1 mounted end==================");
  },
  beforeUpdate() {
    console.log("==============Children1 beforeUpdate start==================");

    console.log("==============Children1 beforeUpdate end==================");
  },
  updated() {
    console.log("==============Children1 updated start==================");

    console.log("==============Children1 updated end==================");
  },
  beforeDestroy() {
    console.log("==============Children1 beforeDestroy start==================");
    console.log(this.$el);
    console.log(this.render);
    console.log("==============Children1 beforeDestroy end==================");
  },
  destroyed() {
    console.log("==============Children1 destroyed start==================");
    console.log(this.$el);
    console.log(this.render);
    console.log("==============Children1 destroyed end==================");
  }
};
</script>

<style scoped>
.aa {
  color: red;
}
</style>

父组件

<script>
export default {
  name: "Base",
  data() {
    return {
      // render: "Base Render"
    };
  },
  computed: {
    render() {
      return this.aa
    }
  },
  beforeCreate() {
    console.log("==============Base beforeCreate start==================");
    console.log(this.render);
    console.log("==============Base beforeCreate end==================");
  },
  created() {
    console.log("==============Base created start==================");
    console.log(this.$el);
    console.log(this.render);
    console.log("==============Base created end==================");
  },
  beforeMount() {
    console.log("==============Base beforeMount start==================");
    console.log(this.$el);
    console.log("==============Base beforeMount end==================");
  },
  mounted() {
    console.log("==============Base mounted start==================");
    console.log(this.$el);
    console.log("==============Base mounted end==================");
  },
  beforeUpdate() {
    console.log("==============Base beforeUpdate start==================");

    console.log("==============Base beforeUpdate end==================");
  },
  updated() {
    console.log("==============Base updated start==================");

    console.log("==============Base updated end==================");
  },
  beforeDestroy() {
    console.log("==============Base beforeDestroy start==================");
    console.log(this.$el);
    console.log(this.render);
    console.log("==============Base beforeDestroy end==================");
  },
  destroyed() {
    console.log("==============Base destroyed start==================");
    console.log(this.$el);
    console.log(this.render);
    console.log("==============Base destroyed end==================");
  }
};
</script>

<style scoped>
.aa {
  color: yellow;
  font-size: 24px;
}
</style>

经过验证,官网介绍是对的(当然人家自己写的肯定是对的 O(∩_∩)O~),但是还有一个问题,组件里不止有js,还有HTML和css,那么他们是什么逻辑呢?
官网没说,没说不要紧,咱们可以自己来。
经过我自己的验证:

  1. css是根据选择器去合并和覆盖的;
  2. HTML则在组件对象中找不到,目前认为父组件中的template标签在继承过程中被抛弃(这里笔者不太确定,如有知道确切结果的欢迎指教)。

综上,得出继承的5点合并原则:

  1. data:递归合并,如有冲突,组件数据优先;
  2. 钩子函数都会调用,组件自身钩子后执行;
  3. 值为对象的选项,只会进行第一层合并,如有冲突,组件数据优先;
  4. css是根据选择器去合并和覆盖的;
  5. 父组件中的template标签在继承过程中被抛弃。

vYong
1 声望0 粉丝

一个喜欢赚钱的程序猿。长期从事写代码和网络兼职赚钱工作