我们知道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监听两个来自子组件的事件increase和reduce,然后在子组件使用Observer.$emit触发父组件的监听事件(看handleIncre、handleReduce方法中代码)。
以上就是个人对Vue组件通信的一点理解。