5分钟理解Vue组件通信的原理

2,239 阅读1分钟

我们知道Vue组件间通信可分为父子组件通信、兄弟组件通信、跨级组件通信。当组件进行通信的时候就会涉及到事件的触发与事件的监听。事件的触发与监听本质上就是一个观察者模式,它解决了对象(组件)间的一种一(父组件或最顶层组件)对多(子组件或跨级组件)的依赖关系,当一个对象的状态(vue中的data)发生改变时,所有依赖它的对象都会得到通知,vue通过这种模式来解决不同层级关系的组件通信。

我们实现一个简易的观察者对象:

var Observer = (function() {
  // 存储事件队列
  var __message = {};
  return {
    // 事件注册监听
    $on: function(type, fn) {
      if (typeof __message[type] === "undefined") {
        __message[type] = [fn];
      } else {
        __message[type].push(fn);
      }
    },
    // 事件触发
    $emit: function(type, args) {
      if (!__message[type]) return;
      var events = {
        type: type,
        args: args || {}
      };
      var len = __message[type].length;
      for (let i = 0; i < len; i++) {
        __message[type][i].call(this, events);
      }
    },
    // 事件移除
    $remove: function(type, fn) {
      var i = __message[type].length - 1;
      for (; i >= 0; i--) {
        __message[type][i] === fn && __message[type].splice(i, 1);
      }
    }
  };
})();

  • Observer.$on注册事件监听,相当于v-on:test="handler"或@test="handler"
  • Observer.$emit触发父组件的事件监听
  • Observer.$remove()移除事件,组件销毁时需要做的事情,相当于一个钩子

然后使用这个观察者对象:

var handler = function(e) {
  console.log("父组件:", e.type, e.args.msg);
};

// 注册事件监听
Observer.$on("test", handler);

// 触发事件监听
Observer.$emit("test", { msg: "子组件第一次更改data" });
Observer.$emit("test", { msg: "子组件第二次更改data" });

// 移除事件监听
Observer.$remove("test", handler);

Observer.$emit("test", { msg: "第三次更改data" });

输出的结果为:

我们触发了三次test事件,可以看到我们并没有输出第三次更改data,因为我们在第二次后移除了事件监听,也就是相当于移除了子组件。实际上vue是在什么时机做上述的事情呢?

我们知道Vue使用Object.defineProperty侦测对象的变化,看下面的伪代码:

Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function() {
    // 收集依赖
    Observer.$on("test", handler);
    return val;
  },
  set: function(newVal) {
    if (val === newVal) return false;
    val = newVal;
    // 触发依赖
    Observer.$emit("test", { msg: "子组件1更改data" });
  }
});

我们看到Vue在getter中收集依赖,在setter中触发依赖,然后通过观察者对象做调度。对比实际书写的vue代码理解:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p>count:{{total}}</p>
        <v-count @increase="handelGetTotal" @reduce="handelGetTotal">
        </v-count>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
        Vue.component('v-count', {
            template: `
                <div>
                    <button @click="handleIncrease">+1</button>
                    <button @click="handleReduce">-1</button>
                </div>
            `,
            data: function () {
                return {
                    counter: 0
                };
            },
            methods: {
                handleIncrease: function () {
                    this.counter++;
                    this.$emit('increase', this.counter);
                },
                handleReduce: function () {
                    this.counter--;
                    this.$emit('reduce', this.counter);
                }
            }
        });
        var app = new Vue({
            el: '#app',
            data: {
                total: 0
            },
            methods: {
                handelGetTotal: function (total) {
                    this.total = total;
                }
            }
        })
    </script>
</body>

</html>

忽略具体的处理逻辑,这个例子相当于使用Observer.$on监听两个来自子组件的事件increasereduce,然后在子组件使用Observer.$emit触发父组件的监听事件(看handleIncrehandleReduce方法中代码)。

以上就是个人对Vue组件通信的一点理解。