Vue.js 组件通信精髓归纳

论坛 期权论坛 期权     
前端大学   2019-6-9 21:29   4156   0
(给前端大学加星标,提升前端技能.)
作者:张炳
https://segmentfault.com/a/1190000018241972
[h3]组件的分类[/h3]常规页面组件由
  1. vue-router
复制代码
产生的每个页面,它本质上也是一个组件(
  1. .vue
复制代码
),主要承载当前页面的 HTML 结构,会包含数据获取、数据整理、数据可视化等常规业务。
功能性抽象组件不包含业务,独立、具体功能的基础组件,比如日期选择器、弹窗警告等。这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。
业务组件它不像第二类独立组件只包含某个功能,而是在业务中被多个页面复用的,它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。
[h3]组件的关系[/h3]父子组件父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 B 就是子组件。
    1. // 注册一个子组件
    复制代码
    1. Vue.component('child', {
    复制代码
    1.     data: function(){
    复制代码
    1.         return {
    复制代码
    1.             text: '我是father的子组件!'
    复制代码
    1.         }
    复制代码
    1.     },
    复制代码
    1.     template: '{{ text }}'
    复制代码
    1. })
    复制代码
    1. // 注册一个父组件
    复制代码
    1. Vue.component('father', {
    复制代码
    1.     template: ''  // 在模板中使用了child组件
    复制代码
    1. })
    复制代码
兄弟组件两个组件互不引用,则为兄弟组件。
    1. Vue.component('brother1', {
    复制代码
    1.     template: '我是大哥'
    复制代码
    1. })
    复制代码
    1. Vue.component('brother2', {
    复制代码
    1.     template: '我是小弟'
    复制代码
    1. })
    复制代码
使用组件的时候:
    1. [/code]
    2. [*][code]   
    复制代码
    1.    
    复制代码
    1. [/code]
    2. [/list]跨级组件就是在父子关系中,中间跨了很多个层级。
    3. [h3]组件的构成[/h3]一个再复杂的组件,都是由三部分组成的: [code]prop
    复制代码
    1. event
    复制代码
    1. slot
    复制代码
    ,它们构成了 Vue.js 组件的 API。
    属性 prop
    1. prop
    复制代码
    定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,
    1. props
    复制代码
    最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用
    1. props
    复制代码
    的数组用法,这样的组件往往是不严谨的。
    插槽 slot插槽
    1. slot
    复制代码
    ,它可以分发组件的内容。和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
      1. [/code]
      2. [*][code]  Something bad happened.
      复制代码
      1. [/code]
      2. [/list]可能会渲染出这样的东西:
      3. [list=1][*][code]Error!Something bad happended.
      复制代码
    幸好,Vue 自定义的
    1. [/code] 元素让这变得非常简单:
    2. [list=1][*][code]Vue.component('alert-box', {
    复制代码
    1.   template: `
    复制代码
    1.    
    复制代码
    1.       Error!
    复制代码
    1.       
    复制代码
    1.    
    复制代码
    1.   `
    复制代码
    1. })
    复制代码
如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!
自定义事件 event两种写法:
1、在组件内部自定义事件 event
    1. [/code]
    2. [*][code]  
    复制代码
    1.    
    复制代码
    1.   
    复制代码
    1. [/code]
    2. [*][code]
    复制代码
    1.   export default {
    复制代码
    1.     methods: {
    复制代码
    1.       handleClick (event) {
    复制代码
    1.         this.$emit('on-click', event);
    复制代码
    1.       }
    复制代码
    1.     }
    复制代码
    1.   }
    复制代码
    1. [/code]
    2. [/list]通过 [code]$emit
    复制代码
    ,就可以触发自定义的事件
    1. on-click
    复制代码
    ,在父级通过
    1. @on-click
    复制代码
    来监听:
      1. [/code]
      2. [/list]2、用事件修饰符 [code].native
      复制代码
      直接在父级声明
      所以上面的示例也可以这样写:
        1. [/code]
        2. [/list]如果不写 [code].native
        复制代码
        修饰符,那上面的
        1. @click
        复制代码
        就是自定义事件
        1. click
        复制代码
        ,而非原生事件
        1. click
        复制代码
        ,但我们在组件内只触发了
        1. on-click
        复制代码
        事件,而不是
        1. click
        复制代码
        ,所以直接写
        1. @click
        复制代码
        会监听不到。
        [h3]组件的通信[/h3]ref和$parent和$childrenVue.js 内置的通信手段一般有两种:
          1. ref
          复制代码
          :给元素或组件注册引用信息;
          1. $parent
          复制代码
          /
          1. $children
          复制代码
          :访问父 / 子实例。
        1. ref
        复制代码
        来访问组件(部分代码省略):
          1. // component-a
          复制代码
          1. export default {
          复制代码
          1.   data () {
          复制代码
          1.     return {
          复制代码
          1.       title: 'Vue.js'
          复制代码
          1.     }
          复制代码
          1.   },
          复制代码
          1.   methods: {
          复制代码
          1.     sayHello () {
          复制代码
          1.       window.alert('Hello');
          复制代码
          1.     }
          复制代码
          1.   }
          复制代码
          1. }
          复制代码
          1. [/code]
          2. [*][code]  
          复制代码
          1. [/code]
          2. [*][code]
          复制代码
          1.   export default {
          复制代码
          1.     mounted () {
          复制代码
          1.       const comA = this.$refs.comA;
          复制代码
          1.       console.log(comA.title);  // Vue.js
          复制代码
          1.       comA.sayHello();  // 弹窗
          复制代码
          1.     }
          复制代码
          1.   }
          复制代码
          1. [/code]
          2. [/list][code]$parent
          复制代码
          1. $children
          复制代码
          类似,也是基于当前上下文访问父组件或全部子组件的。
          这两种方法的弊端是,无法在跨级或兄弟间通信,比如下面的结构:
            1. // parent.vue
            复制代码
            1. [/code]
            2. [*][code]
            复制代码
            1. [/code]
            2. [/list]我们想在 [code]component-a
            复制代码
            中,访问到引用它的页面中(这里就是
            1. parent.vue
            复制代码
            )的两个
            1. component-b
            复制代码
            组件,那这种情况下,是暂时无法实现的,后面会讲解到方法。
            provide / inject一种无依赖的组件通信方法:Vue.js 内置的
            1. provide
            复制代码
            /
            1. inject
            复制代码
            接口。
            1. provide
            复制代码
            /
            1. inject
            复制代码
            是 Vue.js 2.2.0 版本后新增的 API,在文档中这样介绍 :
            这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
            1. provide
            复制代码
            1. inject
            复制代码
            主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
            假设有两个组件:
            1. A.vue
            复制代码
            1. B.vue
            复制代码
            ,B 是 A 的子组件:
              1. // A.vue
              复制代码
              1. export default {
              复制代码
              1.   provide: {
              复制代码
              1.     name: 'Aresn'
              复制代码
              1.   }
              复制代码
              1. }
              复制代码

              1. // B.vue
              复制代码
              1. export default {
              复制代码
              1.   inject: ['name'],
              复制代码
              1.   mounted () {
              复制代码
              1.     console.log(this.name);  // Aresn
              复制代码
              1.   }
              复制代码
              1. }
              复制代码
            需要注意的是:
            1. provide
            复制代码
            1. inject
            复制代码
            绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
            只要一个组件使用了
            1. provide
            复制代码
            向下提供数据,那其下所有的子组件都可以通过
            1. inject
            复制代码
            来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,那这个组件中就不能再声明 这个数据了,因为它已经被父级占有。
            1. provide
            复制代码
            /
            1. inject
            复制代码
            API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:
            • 父组件向子组件(支持跨级)传递数据;
            • 子组件向父组件(支持跨级)传递数据。
            这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,下面介绍一种在父子组件间通信的方法
            1. dispatch
            复制代码
            1. broadcast
            复制代码

            $attrs和$listeners如果父组件 A 下面有子组件 B,组件 B 下面有组件 C,这时如果组件 A 想传递数据给组件C怎么办呢? Vue 2.4 开始提供了
            1. $attrs
            复制代码
            1. $listeners
            复制代码
            来解决这个问题,能够让组件 A 之间传递消息给组件 C。
              1. Vue.component('C',{
              复制代码
              1.         template:`
              复制代码
              1.             
              复制代码
              1.                  
              复制代码
              1.         `,
              复制代码

              1.         methods:{
              复制代码
              1.             passCData(val){
              复制代码
              1.                 //触发父组件A中的事件
              复制代码
              1.                 this.$emit('getCData',val)
              复制代码
              1.             }
              复制代码
              1.         }
              复制代码
              1.     })
              复制代码

              1.     Vue.component('B',{
              复制代码
              1.         data(){
              复制代码
              1.             return {
              复制代码
              1.                 mymessage:this.message
              复制代码
              1.             }
              复制代码
              1.         },
              复制代码
              1.         template:`
              复制代码
              1.             
              复制代码
              1.                  
              复制代码
              1.                
              复制代码
              1.                
              复制代码
              1.                
              复制代码
              1.             
              复制代码
              1.         `,
              复制代码
              1.         props:['message'],//得到父组件传递过来的数据
              复制代码
              1.         methods:{
              复制代码
              1.             passData(val){
              复制代码
              1.                 //触发父组件中的事件
              复制代码
              1.                 this.$emit('getChildData',val)
              复制代码
              1.             }
              复制代码
              1.         }
              复制代码
              1.     })
              复制代码
              1.     Vue.component('A',{
              复制代码
              1.         template:`
              复制代码
              1.             
              复制代码
              1.                 this is parent compoent!
              复制代码
              1.                
              复制代码
              1.             
              复制代码
              1.         `,
              复制代码
              1.         data(){
              复制代码
              1.             return {
              复制代码
              1.                 message:'hello',
              复制代码
              1.                 messagec:'hello c' //传递给c组件的数据
              复制代码
              1.             }
              复制代码
              1.         },
              复制代码
              1.         methods:{
              复制代码
              1.             getChildData(val){
              复制代码
              1.                 console.log('这是来自B组件的数据')
              复制代码
              1.             },
              复制代码
              1.             //执行C子组件触发的事件
              复制代码
              1.             getCData(val){
              复制代码
              1.                 console.log("这是来自C组件的数据:"+val)
              复制代码
              1.             }
              复制代码
              1.         }
              复制代码
              1.     })
              复制代码
              1.     var app=new Vue({
              复制代码
              1.         el:'#app',
              复制代码
              1.         template:`
              复制代码
              1.             
              复制代码
              1.                
              复制代码
              1.             
              复制代码
              1.         `
              复制代码
              1.     })
              复制代码
            派发与广播——自行实现 dispatch 和 broadcast 方法要实现的
            1. dispatch
            复制代码
            1. broadcast
            复制代码
            方法,将具有以下功能:在子组件调用
            1. dispatch
            复制代码
            方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过
            1. $on
            复制代码
            监听了这个事件;相反,在父组件调用
            1. broadcast
            复制代码
            方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。
              1. // 部分代码省略
              复制代码
              1. import Emitter from '../mixins/emitter.js'
              复制代码

              1. export default {
              复制代码
              1.   mixins: [ Emitter ],
              复制代码
              1.   methods: {
              复制代码
              1.     handleDispatch () {
              复制代码
              1.       this.dispatch();  // ①
              复制代码
              1.     },
              复制代码
              1.     handleBroadcast () {
              复制代码
              1.       this.broadcast();  // ②
              复制代码
              1.     }
              复制代码
              1.   }
              复制代码
              1. }
              复制代码
              1. //emitter.js 的代码:
              复制代码
              1. function broadcast(componentName, eventName, params) {
              复制代码
              1.   this.$children.forEach(child => {
              复制代码
              1.     const name = child.$options.name;
              复制代码

              1.     if (name === componentName) {
              复制代码
              1.       child.$emit.apply(child, [eventName].concat(params));
              复制代码
              1.     } else {
              复制代码
              1.       broadcast.apply(child, [componentName, eventName].concat([params]));
              复制代码
              1.     }
              复制代码
              1.   });
              复制代码
              1. }
              复制代码
              1. export default {
              复制代码
              1.   methods: {
              复制代码
              1.     dispatch(componentName, eventName, params) {
              复制代码
              1.       let parent = this.$parent || this.$root;
              复制代码
              1.       let name = parent.$options.name;
              复制代码

              1.       while (parent && (!name || name !== componentName)) {
              复制代码
              1.         parent = parent.$parent;
              复制代码

              1.         if (parent) {
              复制代码
              1.           name = parent.$options.name;
              复制代码
              1.         }
              复制代码
              1.       }
              复制代码
              1.       if (parent) {
              复制代码
              1.         parent.$emit.apply(parent, [eventName].concat(params));
              复制代码
              1.       }
              复制代码
              1.     },
              复制代码
              1.     broadcast(componentName, eventName, params) {
              复制代码
              1.       broadcast.call(this, componentName, eventName, params);
              复制代码
              1.     }
              复制代码
              1.   }
              复制代码
              1. };
              复制代码
            因为是用作
            1. mixins
            复制代码
            导入,所以在
            1. methods
            复制代码
            里定义的
            1. dispatch
            复制代码
            1. broadcast
            复制代码
            方法会被混合到组件里,自然就可以用
            1. this.dispatch
            复制代码
            1. this.broadcast
            复制代码
            来使用。
            这两个方法都接收了三个参数,第一个是组件的
            1. name
            复制代码
            值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。
            可以看到,在
            1. dispatch
            复制代码
            里,通过
            1. while
            复制代码
            语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量
            1. parent
            复制代码
            即为父组件实例),直到匹配到定义的
            1. componentName
            复制代码
            与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件
            1. eventName。broadcast
            复制代码
            方法与之类似,只不过是向下遍历寻找。
            来看一下具体的使用方法。有
            1. A.vue
            复制代码
            1. B.vue
            复制代码
            两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:
              1. [/code]
              2. [*][code]
              复制代码
              1.     触发事件
              复制代码
              1. [/code]
              2. [*][code]
              复制代码
              1.   import Emitter from '../mixins/emitter.js';
              复制代码

              1.   export default {
              复制代码
              1.     name: 'componentA',
              复制代码
              1.     mixins: [ Emitter ],
              复制代码
              1.     methods: {
              复制代码
              1.       handleClick () {
              复制代码
              1.         this.broadcast('componentB', 'on-message', 'Hello Vue.js');
              复制代码
              1.       }
              复制代码
              1.     }
              复制代码
              1.   }
              复制代码
              1. [/code]
              2. [/list][list=1][*][code]// B.vue
              复制代码
              1. export default {
              复制代码
              1.   name: 'componentB',
              复制代码
              1.   created () {
              复制代码
              1.     this.$on('on-message', this.showMessage);
              复制代码
              1.   },
              复制代码
              1.   methods: {
              复制代码
              1.     showMessage (text) {
              复制代码
              1.       window.alert(text);
              复制代码
              1.     }
              复制代码
              1.   }
              复制代码
              1. }
              复制代码
            同理,如果是 B 向 A 通信,在 B 中调用
            1. dispatch
            复制代码
            方法,在 A 中使用 $on 监听事件即可。
            以上就是自行实现的
            1. dispatch
            复制代码
            1. broadcast
            复制代码
            方法。
            找到任意组件实例——findComponents 系列方法它适用于以下场景:
            • 由一个组件,向上找到最近的指定组件;
            • 由一个组件,向上找到所有的指定组件;
            • 由一个组件,向下找到最近的指定组件;
            • 由一个组件,向下找到所有指定的组件;
            • 由一个组件,找到指定组件的兄弟组件。
            5 个不同的场景,对应 5 个不同的函数,实现原理也大同小异。
            1、向上找到最近的指定组件——findComponentUpward
              1. // 由一个组件,向上找到最近的指定组件
              复制代码
              1. function findComponentUpward (context, componentName) {
              复制代码
              1.   let parent = context.$parent;
              复制代码
              1.   let name = parent.$options.name;
              复制代码

              1.   while (parent && (!name || [componentName].indexOf(name)
              复制代码
              1. [/code]
              2. [*][code]  
              复制代码
              1.     组件 B
              复制代码
              1.   
              复制代码
              1. [/code]
              2. [*][code]
              复制代码
              1.   import { findComponentUpward } from '../utils/assist.js';
              复制代码

              1.   export default {
              复制代码
              1.     name: 'componentB',
              复制代码
              1.     mounted () {
              复制代码
              1.       const comA = findComponentUpward(this, 'componentA');
              复制代码

              1.       if (comA) {
              复制代码
              1.         console.log(comA.name);  // Aresn
              复制代码
              1.         comA.sayHello();  // Hello, Vue.js
              复制代码
              1.       }
              复制代码
              1.     }
              复制代码
              1.   }
              复制代码
              1. [/code]
              2. [/list]2、向上找到所有的指定组件——findComponentsUpward
              3. [list=1][*][code]// 由一个组件,向上找到所有的指定组件
              复制代码
              1. function findComponentsUpward (context, componentName) {
              复制代码
              1.   let parents = [];
              复制代码
              1.   const parent = context.$parent;
              复制代码

              1.   if (parent) {
              复制代码
              1.     if (parent.$options.name === componentName) parents.push(parent);
              复制代码
              1.     return parents.concat(findComponentsUpward(parent, componentName));
              复制代码
              1.   } else {
              复制代码
              1.     return [];
              复制代码
              1.   }
              复制代码
              1. }
              复制代码
              1. export { findComponentsUpward };
              复制代码
            3、向下找到最近的指定组件——findComponentDownward
              1. // 由一个组件,向下找到最近的指定组件
              复制代码
              1. function findComponentDownward (context, componentName) {
              复制代码
              1.   const childrens = context.$children;
              复制代码
              1.   let children = null;
              复制代码

              1.   if (childrens.length) {
              复制代码
              1.     for (const child of childrens) {
              复制代码
              1.       const name = child.$options.name;
              复制代码

              1.       if (name === componentName) {
              复制代码
              1.         children = child;
              复制代码
              1.         break;
              复制代码
              1.       } else {
              复制代码
              1.         children = findComponentDownward(child, componentName);
              复制代码
              1.         if (children) break;
              复制代码
              1.       }
              复制代码
              1.     }
              复制代码
              1.   }
              复制代码
              1.   return children;
              复制代码
              1. }
              复制代码
              1. export { findComponentDownward };
              复制代码
            4、向下找到所有指定的组件——findComponentsDownward
              1. // 由一个组件,向下找到所有指定的组件
              复制代码
              1. function findComponentsDownward (context, componentName) {
              复制代码
              1.   return context.$children.reduce((components, child) => {
              复制代码
              1.     if (child.$options.name === componentName) components.push(child);
              复制代码
              1.     const foundChilds = findComponentsDownward(child, componentName);
              复制代码
              1.     return components.concat(foundChilds);
              复制代码
              1.   }, []);
              复制代码
              1. }
              复制代码
              1. export { findComponentsDownward };
              复制代码
            5、找到指定组件的兄弟组件——findBrothersComponents
              1. // 由一个组件,找到指定组件的兄弟组件
              复制代码
              1. function findBrothersComponents (context, componentName, exceptMe = true) {
              复制代码
              1.   let res = context.$parent.$children.filter(item => {
              复制代码
              1.     return item.$options.name === componentName;
              复制代码
              1.   });
              复制代码
              1.   let index = res.findIndex(item => item._uid === context._uid);
              复制代码
              1.   if (exceptMe) res.splice(index, 1);
              复制代码
              1.   return res;
              复制代码
              1. }
              复制代码
              1. export { findBrothersComponents };
              复制代码
            相比其它 4 个函数,
            1. findBrothersComponents
            复制代码
            多了一个参数
            1. exceptMe
            复制代码
            ,是否把本身除外,默认是
            1. true
            复制代码
            。寻找兄弟组件的方法,是先获取
            1. context.$parent.$children
            复制代码
            ,也就是父组件的全部子组件,这里面当前包含了本身,所有也会有第三个参数
            1. exceptMe
            复制代码
            。Vue.js 在渲染组件时,都会给每个组件加一个内置的属性
            1. _uid
            复制代码
            ,这个
            1. _uid
            复制代码
            是不会重复的,借此我们可以从一系列兄弟组件中把自己排除掉。
            Event Bus有时候两个组件之间需要进行通信,但是它们彼此不是父子组件的关系。在一些简单场景,你可以使用一个空的 Vue 实例作为一个事件总线中心(central event bus):
              1. //中央事件总线
              复制代码
              1.     var bus=new Vue();
              复制代码

              1.     var app=new Vue({
              复制代码
              1.         el:'#app',
              复制代码
              1.         template:`
              复制代码
              1.             
              复制代码
              1.                
              复制代码
              1.                
              复制代码
              1.             
              复制代码
              1.         `
              复制代码
              1.     })
              复制代码

              1. // 在组件 brother1 的 methods 方法中触发事件
              复制代码
              1. bus.$emit('say-hello', 'world')
              复制代码

              1. // 在组件 brother2 的 created 钩子函数中监听事件
              复制代码
              1. bus.$on('say-hello', function (arg) {
              复制代码
              1.   console.log('hello ' + arg);          // hello world
              复制代码
              1. })
              复制代码
            vuex处理组件之间的数据交互如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex 的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 详情可参考:https://vuex.vuejs.org/zh-cn/。
            [h3]参考[/h3]
            • vue组件之间8种组件通信方式总结(https://blog.csdn.net/zhoulu001/article/details/79548350)
            • https://github.com/iview/iview/blob/2.0/src/mixins/emitter.js
            • https://github.com/iview/iview/blob/2.0/src/utils/assist.js
            免费福利

            好文和朋友一起点个在看~
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:30
帖子:6
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP