Skip to content

Commit e4e4932

Browse files
committed
组件间通信
1 parent 3c4cac8 commit e4e4932

File tree

1 file changed

+214
-5
lines changed
  • vue/组件间通信

1 file changed

+214
-5
lines changed

vue/组件间通信/1.md

+214-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Vue.js 在现今使用有多广泛不用多说,而 Vue 的一大特点就是
55

66
【前端进阶良品】会作为一个新系列连载,后续会更多优质前端内容,感兴趣的同学不妨关注一下。
77
文章最后有 **交流群****公众号**,可以一起学习交流,感谢🍻。
8+
* 下期预告:深入 Vue 响应式原理,手写一个 mvvm
89
## 组件
910
> 组件是可以复用的 Vue 实例。 — [Vue 官方文档](https://cn.vuejs.org/v2/guide/components.html)
1011
@@ -318,7 +319,7 @@ mounted() {
318319
```
319320
那什么情况下需要获取组件实例呢?比如父元素的某个状态改变,需要子组件进行 http 请求更新数据。通常情况下,我们会选择通过 Props 将状态传递给子组件,然后子组件进行 Watch 监测,如果有变更,则进行相应操作。这个时候,我们便可以选择使用 ref。
320321
``` html
321-
<child-component ref="child"></child-component>
322+
<child ref="child"></child>
322323
···
323324
<script>
324325
export default {
@@ -345,8 +346,216 @@ mounted() {
345346
</script>
346347
```
347348
`$parent``$children` 用法一样,不过 `$parent` 返回的父组件实例,不是数组,因为父组件肯定只有一个。ref、parent、children 它们几个的一个缺点就是无法处理跨级组件和兄弟组件,后续我们会介绍 dispatch 和 broadcast 方法,实现跨级通信。
348-
### emit、on
349-
`$emit`,想必大家都非常熟悉,我们通常用作父子组件间通信。`$emit``$on`都是组件自身的方法,`$on` 可以监听 `$emit` 派发的事件,这也是我们下一个通信方式 EventBus 所依赖的原理。
349+
### emit、on、off
350+
`$emit`,想必大家都非常熟悉,我们通常用作父子组件间通信,我们也叫它自定义事件`$emit``$on`都是组件自身的方法,`$on` 可以监听 `$emit` 派发的事件,`$off` 则用来取消事件监听。这也是我们下一个要讲的通信方式 EventBus 所依赖的原理。
350351
``` html
351-
352-
```
352+
// 父组件
353+
<template>
354+
<button-component @clickButton="clickButton"></button-component>
355+
// 在父组件利用 v-on 监听
356+
</template>
357+
<script>
358+
export default {
359+
methods: {
360+
clickButton () { ··· }
361+
}
362+
}
363+
</script>
364+
365+
// 子组件
366+
<template>
367+
<button @click="handleClick"></button>
368+
</template>
369+
<script>
370+
export default {
371+
methods: {
372+
handleClick () { // 触发 $emit
373+
this.$emit('clickButton');
374+
}
375+
},
376+
mounted() {
377+
this.$on('clickButton', (...arr) => { // 也可以自己监听 $emit,虽然没什么用···
378+
console.log(...arr);
379+
})
380+
}
381+
}
382+
</script>
383+
```
384+
### EventBus
385+
`$emit`的痛点依然是支持跨级和兄弟组件,Vue 官方推荐我们使用一个新的 Vue 实例来做一个全局的事件通信(或者叫中央事件总线···),也就是我们要讲的 EventBus。了解过的同学都知道,正常的 bus,我们一般会挂载到 Vue 的 prototype 上,方便全局调用。
386+
``` javascript
387+
// main.js
388+
Vue.prototype.$bus = new Vue();
389+
```
390+
依旧改写之前的栗子:
391+
``` html
392+
<!--communication.vue-->
393+
<communication-sub v-bind="dataProps" >
394+
</communication-sub>
395+
···
396+
beforeDestroy() { <!-- 实例销毁时,需要卸载监听事件 -->
397+
this.$bus.$off('busClick');
398+
},
399+
created() { <!-- 监听子组件触发的 Bus 事件-->
400+
this.$bus.$on('busClick', (data) => {
401+
this.dataProps.title = data;
402+
});
403+
}
404+
405+
<!--communication-min-sub.vue-->
406+
<template>
407+
<div class="communication-min-sub">
408+
<button @click="busClick">click bus</button>
409+
<!--子组件触发点击事件-->
410+
</div>
411+
</template>
412+
<script>
413+
export default{
414+
methods: {
415+
busClick() {
416+
this.$bus.$emit('busClick', 'bus 触发了');
417+
}
418+
}
419+
}
420+
</script>
421+
```
422+
这是一个基础的 EventBus 的实现。现在我们设想一下,类似于 userInfo 这样的信息,在很多页面都需要用到,那我们需要在许多页面都做 `$on` 监听的操作。那能否将这些操作整合到一起呢?我们一起来看:
423+
``` javascript
424+
// 新建一个 eventBus.js
425+
import Vue from 'vue';
426+
const bus = new Vue({
427+
data () {
428+
return {
429+
userInfo: {}
430+
}
431+
},
432+
created () {
433+
this.$on('getUserInfo', val => {
434+
this.userInfo = val;
435+
})
436+
}
437+
});
438+
export default bus;
439+
// main.js
440+
import bus from './eventBus';
441+
Vue.prototype.$bus = bus;
442+
// app.vue
443+
methods: {
444+
getUserInfo() {
445+
ajax.post(***).then(data => {
446+
this.$bus.$emit('getUserInfo', data); // 通知 EventBus 更新 userInfo
447+
})
448+
}
449+
}
450+
```
451+
这样在其他页面用到 userInfo 的时候,只需要 `this.$bus.userInfo` 就可以了。注意刚刚其实没有用 off 卸载掉监听,因为其实 userInfo 这种全局信息,并没有一个准确的说要销毁的时机,浏览器关闭的时候,也用不着我们处理了。但是,如果只是某个页面组件用到的,建议还是用最开始的方法,在页面销毁的时候卸载掉。
452+
453+
不过反过来讲,既然用到了 EventBus,说明状态管理并不复杂,否则还是建议用 Vuex 来做。最后再给大家推荐一篇文章 [Vue中eventbus很头疼?我来帮你](https://juejin.im/post/5b45971ff265da0f9c678b55),作者处理 EventBus 的思路很巧妙,大家不妨仔细看看。
454+
### 派发与广播:dispatch 与 broadcast
455+
> 此部分参考自 [Element 源码](https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js)
456+
457+
如果有接触过 Vue.js 1.x 的同学,应该对此有所了解。在 1.x 的实现中,是有 `$dispatch``$broadcast` 方法的。`$dispatch` 的主要作用是向上级组件派发事件,`$broadcast` 则是向下级广播。它们的优点是都支持跨级,再看一下官方废弃这两个方法的理由:
458+
> 因为基于组件树结构的事件流方式实在是让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。并且 `$dispatch``$broadcast` 也没有解决兄弟组件间的通信问题。
459+
460+
可以看到,主要原因是在组件结构扩展后不易理解,以及没有解决兄弟组件通信的问题。但是对于组件库来说,这依旧是十分有用的,所以它们大多自己实现了这两个方法。对我们来讲,也许在项目中用不到,但学习这种解决问题的思路,是十分必要的。
461+
462+
派发和广播,依赖于组件的 name(最怕此处有人说:如果不写 name,这方法不就没用了?2333···),以此来逐级查找对应的组件实例。Element 的实现中,给所有的组件都加了一个 componentName 属性,所以它是根据 componentName 来查找的。我们在实现的时候还是直接用 name。
463+
464+
我们先来看一下 `$dispatch` 的简单用法,再来分析思路。
465+
``` html
466+
<!--communication-min-sub.vue-->
467+
<template>
468+
<button @click="handleDispatch">dispatch</button>
469+
</template>
470+
<script>
471+
import Emitter from '../../utils/emitter';
472+
export default {
473+
mixins: [Emitter], // 混入,方便直接调用
474+
methods: {
475+
handleDispatch () {
476+
this.dispatch('communication', 'onMessage', '触发了dispatch');
477+
}
478+
}
479+
}
480+
</script>
481+
```
482+
``` html
483+
<!--communication.vue-->
484+
<script>
485+
export default {
486+
beforeDestroy() { // 销毁
487+
this.$off('onMessage');
488+
},
489+
mounted () {
490+
this.$on('onMessage', (data) => { // 监听
491+
this.dataProps.title = data;
492+
})
493+
}
494+
}
495+
</script>
496+
```
497+
现在明确一下目标,dispatch 方法接收三个参数,组件 name、事件名称、基础数据(可不传)。要做到向上跨级派发事件,需要向上找到指定 name 的组件实例,利用我们前文提到的 `$emit`方法做派送,所以在指定组件就可以用 `$on` 来监听了。所以 dispatch 本质上就是向上查找到指定组件并触发其自身的 `$emit`,以此来做响应,broadcast 则相反。那么如何做到跨级查找呢?
498+
``` javascript
499+
function broadcast(componentName, eventName, params) {
500+
this.$children.forEach(child => { // 遍历所有的 $children
501+
var name = child.$options.name; // 拿到实例的name,Element 此处用的 componentName
502+
if (name === componentName) { // 如果是想要的那个,进行广播
503+
child.$emit.apply(child, [eventName].concat(params));
504+
} else { // 不是则递归查找 直到 $children 为 []
505+
broadcast.apply(child, [componentName, eventName].concat([params]));
506+
}
507+
});
508+
}
509+
export default {
510+
methods: {
511+
dispatch(componentName, eventName, params) {
512+
var parent = this.$parent || this.$root;
513+
var name = parent.$options.name;
514+
while (parent && (!name || name !== componentName)) {
515+
// 存在 parent 且 (不存在 name 或 name 和 指定参数不一样) 则继续查找
516+
parent = parent.$parent; // 不存在继续取上级
517+
if (parent) {
518+
name = parent.$options.name; // 存在上级 再次赋值并再次循环,进行判断
519+
}
520+
}
521+
if (parent) { // 找到以后 如果有 进行事件派发
522+
parent.$emit.apply(parent, [eventName].concat(params));
523+
}
524+
},
525+
broadcast(componentName, eventName, params) {
526+
broadcast.call(this, componentName, eventName, params);
527+
}
528+
}
529+
};
530+
```
531+
以上是详细的 emitter.js,可以看见,这和我们之前讲到的 `$parent``$children``$emit``$on`都密切相关。这也是为什么把它放到后面讲的原因。之前说过,派发和广播并没有解决兄弟组件通信的问题,所以这里大家也可以拓展思考一下,如何支持兄弟组件间通信。依然是依赖于`$parent``$children`,可以找到任意指定组件。
532+
### Vuex
533+
> Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。--- [官方文档](https://vuex.vuejs.org/zh/)
534+
535+
Vuex 相信大家都比较熟悉了,我不打算在这里把 API 再演示一遍。因为我觉得,官方文档 已经非常详细了。Vuex 的核心是单向数据流,并以相应规则保证所有的状态管理都可追踪、可预测。
536+
537+
我们需要知道什么时候该用 Vuex,如果你的项目比较小,状态管理比较简单,完全没有必要使用 Vuex,你可以考虑我们前文提到的几种方式。
538+
## 总结
539+
本期文章内容到这里就讲完了,我们来总结回顾一下:
540+
* 子组件触达父组件的方式:Props、`$parent``$attrs``$listeners`、provide 和 inject、`$dispatch`
541+
* 父组件触达子组件的方式:`$emit``$on``$children``$ref``broadcast`
542+
* 全局通信:EventBus、Vuex
543+
544+
本来想按照是否支持跨级来分,但是这里的界定比较模糊:如果逐级传递,有些也能做到跨级,但这并不是我们想要的。所以我们只要自己清楚在什么情况下该怎么用就好了。
545+
## 交流群
546+
> qq前端交流群:960807765,欢迎各种技术交流,期待你的加入
547+
## 后记
548+
如果你看到了这里,且本文对你有一点帮助的话,希望你可以动动小手支持一下作者,感谢🍻。文中如有不对之处,也欢迎大家指出,共勉。
549+
550+
* 本文示例 **源码库** [webrtc-stream](https://github.com/wuyawei/webrtc-stream)
551+
* **文章仓库** [🍹🍰fe-code](https://github.com/wuyawei/fe-code)
552+
553+
往期文章:
554+
* [从头到脚撸一个多人视频聊天 — 前端 WebRTC 实战(一)](https://juejin.im/post/5c3acfa56fb9a049f36254be)
555+
* [JavaScript 原型和原型链及 canvas 验证码实践](https://juejin.im/post/5c7b524ee51d453ee81877a7)
556+
* [站住,你这个Promise!](https://juejin.im/post/5c179aad5188256d9832fb61)
557+
* [💘🍦🙈Vchat — 从头到脚,撸一个社交聊天系统(vue + node + mongodb)](https://juejin.im/post/5c0a00fb6fb9a049d4419d3a)
558+
559+
欢迎关注公众号 **前端发动机**,江三疯的前端二三事,专注技术,也会时常迷糊。希望在未来的前端路上,与你一同成长。
560+
561+
![](https://user-gold-cdn.xitu.io/2019/3/16/1698668bd914d63f?w=258&h=258&f=jpeg&s=27979)

0 commit comments

Comments
 (0)