props / $emit

$parent / $children

$dispatch

一直递归找父组件,然后执行父组件中对应的 $emit 方法

1
2
3
4
5
6
7
Vue.prototype.$dispatch = function (eventName, newValue) {
let parent = this.$parent;
while (parent) {
parent.$emit(eventName, newValue);
parent = parent.$parent; // 继续递归接着往上找
}
};

$broadcast

一直往归找子元素,执行对应的方法执行

1
2
3
4
5
6
7
8
9
10
11
12
Vue.prototype.$broadcast = function (eventName, newValue) {
let children = this.$children;
function broad(children) {
children.forEach((child) => {
child.$emit(eventName, newValue);
if (child.$children) {
broad(child.$children);
}
});
}
broad(children);
};

.sync / v-model

props 和 $emit 的语法糖

  • 只需要将 $emit(fn) 中的 fn 改为 update:xxx 就可以了,其中 xxx 为父组件传递给子组件的数据名
  • 在子组件中调用 $emit(fn, val) 的时候 fn 的名字一定是这样的格式的 update:xxx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Parent.vue
<Son @update:number="newValue =>number = newValue" :number="number" />

// Son.vue
<button @click="change">更新父组件的方法</button>
methods: {
change() {
this.$emit('update:number', 200)
}
}

// Parent.vue
<Son @update:number="newValue => number = newValue" :number="number" />
// 等价于
<Son :number.sync="number" />

$attrs 和 $listeners

$attrs 和 v-bind

  • 场景: 要将父组件的数据传递给孙组件,但是我们的子组件又没有使用到这些数据,这个时候可以使用 $attrs 了
  • 局限性: 子组件不可以接收父组件的数据,也就是不可以有 props 钩子
1
2
3
4
5
6
7
8
// Parent.vue
<Son :number="number" :count="count" />

// Son.vue
<Grandson v-bind="$attrs" />

// Grandson.vue
{{ $attrs }}

$listeners 和 v-on

  • 通过 $listeners 和 v-on 的配合可以将全部事件传递给孙组件,子组件也是可以通过 $listeners 来接收父组件传递的全部事件
1
2
3
4
5
6
7
8
9
10
// Parent.vue
<Son :number="number" @change="change" @say="say" :count="count" />

// Son.vue
<Grandson v-on="$listeners" />

// Grandson.vue
mounted() {
console.log(this.$listeners)
}

eventbus

1
2
3
4
5
6
7
8
9
Vue.prototype.$bug = function () {
return new Vue(); // Vue本身就是具有发布订阅能力的
};

// 组件A - 在组件A中发布事件
this.$bus.$on("change", () => {});

// 组件B - 在组件B中可以订阅这个事件实现通信
this.$bus.$emit("change");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class EventEmitter {
constructor() {
this.events = {};
}

// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}

// 发布事件
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach((cb) => {
cb(...args);
});
}
}

// 取消订阅
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
(cb) => cb !== callback
);
}
}
}

// 使用
const ee = new EventEmitter();

// 订阅事件
ee.on("sayHi", () => console.log("Hello!"));

// 发布事件
ee.emit("sayHi"); // Hello!

// 取消订阅
const cb = () => console.log("Hello!");
ee.on("sayHi", cb);
ee.off("sayHi", cb);
ee.emit("sayHi"); // 没有输出

ref

provide / inject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Parent.vue
export default {
provide() {
return {vm: this}
},
}

// Son.vue
export default {
inject: ['vm'],
mounted() {
console.log(this.vm)
}
}