@@ -5,6 +5,7 @@ Vue.js 在现今使用有多广泛不用多说,而 Vue 的一大特点就是
5
5
6
6
【前端进阶良品】会作为一个新系列连载,后续会更多优质前端内容,感兴趣的同学不妨关注一下。
7
7
文章最后有 ** 交流群** 和 ** 公众号** ,可以一起学习交流,感谢🍻。
8
+ * 下期预告:深入 Vue 响应式原理,手写一个 mvvm
8
9
## 组件
9
10
> 组件是可以复用的 Vue 实例。 — [ Vue 官方文档] ( https://cn.vuejs.org/v2/guide/components.html ) ;
10
11
@@ -318,7 +319,7 @@ mounted() {
318
319
```
319
320
那什么情况下需要获取组件实例呢?比如父元素的某个状态改变,需要子组件进行 http 请求更新数据。通常情况下,我们会选择通过 Props 将状态传递给子组件,然后子组件进行 Watch 监测,如果有变更,则进行相应操作。这个时候,我们便可以选择使用 ref。
320
321
``` html
321
- <child-component ref =" child" ></child-component >
322
+ <child ref =" child" ></child >
322
323
···
323
324
<script >
324
325
export default {
@@ -345,8 +346,216 @@ mounted() {
345
346
</script>
346
347
```
347
348
` $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 所依赖的原理。
350
351
``` 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