Vue之组件通信

232 阅读2分钟

vue中组件之间的关系可以分为两类:

  • 父子组件
  • 非父子组件(兄弟组件,隔代组件)

1. props/$emit

父组件通过props向子组件传递数据,只读不可修改。

子组件通过$emit向父组件通信,$emit绑定一个自定义事件,当被执行时,把参数arg传递给父组件,父组件以v-on的方式监听并接收参数。

// comA.vue 父组件
<template>
  <div>
    <!-- 给子组件传值 , @接受子组件定义的事件-->
    <comB :list='list' @onAddIndex='onAddIndex'></comB>
    <p>{{activeIndex}}</p>
  </div>
</template>

<script>
import comB from './comB'
export default {
  name: 'comA',
  components: {comB},
  data() {
    return {
      list: ['11', '22', '33'],
      activeIndex: 0
    }
  },
  methods: {
    onAddIndex(idx) {
       this.activeIndex = idx + 1;
    }
  }    
}
</script>
// comB.vue 子组件
<template>
  <div>
    <p v-for='(item, index) in list' :key='index' @click="addIndex(index)">{{item}}</p>
  </div>
</template>

<script>
export default {
  name: 'comB',
  props: ['list'], //获取父组件的传值
  methods: {
    addIndex(index){
        this.$emit('onAddIndex', index) //给父组件传值
    }
  }
}
</script>

2. $parent/$children

$parent$children 可以访问组件的实例,拿到此组件的所有数据和方法,但是不推荐使用。

$parent是一个对象,$children拿到的是一个数组。

// comA.vue 父组件
<template>
  <div>
    <comB></comB>
    <button @click="changeB">修改B的值</button>
  </div>
</template>

<script>
import comB from './comB'
export default {
  name: 'comA',
  components: {comB},
  data() {
    return {
      list: ['11', '22', '33']
    }
  },
  methods: {
      changeB() {
          this.$children[0].msg = 'newVal' //给子组件传值
      }
  }    
}
</script>
// comB.vue 子组件
<template>
  <div>
    <p v-for='(item, index) in parentList' :key='index'>{{item}}</p>
    <p>{{msg}}</p>
  </div>
</template>

<script>
export default {
  name: 'comB',
  data() {
      return {
          msg: 'oldVal'
      }
  },
  computed: {
      parentList() {
          return this.$parent.list //获取父组件的值
      }
  }
}
</script>

3. provide / inject

父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。

不管层级有多深,解决跨级组件的通信,主要应用是子组件获取上级组件的状态,为高阶组件和组件库提供。

// comA.vue 父组件
<template>
  <div>
    <comB></comB>
  </div>
</template>

<script>
import comB from './comB'
export default {
  name: 'comA',
  components: {comB},
  provide: { //将属性提供给所有的子组件
      name: 'aa'
  }    
}
</script>
// comB.vue 子组件
<template>
  <div>
  </div>
</template>

<script>
export default {
  name: 'comB',
  inject: ['name'], //获取父组件的值
  mounted() {
      console.log(this.name)
  }
}
</script>

provide 和 inject 绑定不是可响应的。但是,如果传入一个可监听的对象,那么其对象的属性还是可响应的。

所以 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的。

4. ref / refs

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM元素。如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据。

// comA.vue 父组件
<template>
  <div>
    <comB ref='comB'></comB>
  </div>
</template>

<script>
import comB from './comB'
export default {
  name: 'comA',
  components: {comB},
  mounted() {
    const B = this.$refs.comB;
    console.log(B.name);
    B.say();
  }    
}
</script>
// comB.vue 子组件
<template>
  <div>
  </div>
</template>

<script>
export default {
  name: 'comB',
  data() {
      return {
          name: 'bb'
      }
  },
  methods: {
      say() {
          console.log('hello')
      }
  }
}
</script>

5. $emit / $on

这种方法通过一个空的Vue实例作为中央事件总线eventBus,所有组件都可以向该中心注册发送事件或接收事件,实现了任何组件间的通信。

// event-bus.js,先创建事件总线并导出
import Vue from 'vue'
export const EventBus = new Vue()
//EventBus.$emit(事件名,数据);
//EventBus.$on(事件名,data => {})
// comA.vue,包含两个组件comB和comC
<template>
  <div>
    <comB ref='comB'></comB>
    <comC ref='comC'></comC>
  </div>
</template>

<script>
import comB from './comB'
import comC from './comC'
export default {
  name: 'comA',
  components: {comB, comC}   
}
</script>
// comB.vue,注册事件
<template>
  <div>
      <button @click="addNum">加法</button>
  </div>
</template>

<script>
import {EventBus} from './event-bus.js'
export default {
  name: 'comB',
  data() {
      return {
          num: 1
      }
  },
  methods: {
      addNum() {
          EventBus.$emit('addition', {
              num: this.num++
          })
      }
  }
}
</script>
// comC.vue,接受事件
<template>
  <div>
      <p>{{count}}</p>
  </div>
</template>

<script>
import {EventBus} from './event-bus.js'
export default {
  name: 'comB',
  data() {
      return {
          count: 0
      }
  },
  mounted() {
    EventBus.$on('addition', param => {
      this.count = this.count + param.num
    })
  }
}
</script>

6. vuex

当项目比较大时,可以使用状态管理器vuex。

详情参考:juejin.cn/post/684490…

7. $attrs / $listeners

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。

如果仅仅是传递数据,而不做中间处理,也可以使用$attrs / $listeners

  • $attrs:存放的是父组件中绑定的非 Props 属性,可以通过 v-bind="$attrs" 传入内部组件。

  • $listeners:包含了父作用域中的 v-on 事件监听器,可以通过 v-on="$listeners" 传入内部组件。

// comA.vue父组件
<template>
  <div>
    <comB
    :name='name'
    :age='age'
    :sex='sex'
    :height='height'
    title='aa'></comB>
  </div>
</template>

<script>
import comB from './comB'
export default {
  name: 'comA',
  components: {comB},
  data() {
      return {
          name: 'xx',
          age: 20,
          sex: 'man',
          height: 180
      }
  } 
}
</script>
// comB.vue子组件
<template>
  <div>
    <comC v-bind="$attrs"></comC>
  </div>
</template>

<script>
import comC from './comC'
export default {
  name: 'comB',
  components: {comC},
  inheritAttrs: false,
  props: {
      name: String //name作为props属性绑定
  },
  created() {
      console.log(this.$attrs);
      //{age: 20, sex: "man", height: 180, title: "aa"},没有name
  }
}
</script>
// comC.vue子组件
<template>
  <div>
      <p>{{$attrs}}</p>
  </div>
</template>

<script>
export default {
  name: 'comB',
  props: {
    age:Number
  },
  created() {
      console.log(this.$attrs);
      //{ "sex": "man", "height": 180, "title": "aa" },没有age
  }
}
</script>

总结

  • 父子组件通信:props / $emit , $parent / $children , provide / inject , ref , $attrs / $listeners
  • 兄弟组件通信:eventBus , vuex
  • 跨级组件通信:eventBus , vuexprovide / inject , $attrs / $listeners