Vue组件的11种通信方式总结

本文总结了Vue组件间通信的11种方式,包括provide/inject、props、$emit、eventBus、vuex、$parent/$children/$refs、$attrs、$listeners、Vue.observable、mixin以及路由传值。详细介绍了每种方式的使用场景和注意事项,帮助开发者灵活应对不同组件间的数据交互需求。

目录

1. provide / inject

2. props(父传子) 

3. $emit(子传父)

4. eventBus(全局创建Vue实例)

5. vuex(状态管理) 

6. $parent / $children / $refs (获取组件实例)

7. $attrs

8. $listeners

9. Vue.observable

10. mixin

11. 路由传值 /引用数据类型值传递实现父子间数据的共享 


组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。
vue组件间的传值方式多种多样,并不局限于父子传值、事件传值这些。

1. provide / inject

这一组选项需要一起使用,允许一个祖先组件向其所有的后代组件注入一个依赖,不论组件层级有多深,并在其上下游关系成立的时间里始终生效.

// provide 选项应该是一个对象或返回一个对象的函数
// inject 选项应该是:一个字符串数组,或一个对象,对象的 key 是本地的绑定名

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo' (数组形式)
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

或 (对象形式)
var Child = {
  inject: {
    foo: {
        from: 'bar', // 可选
        default: 'self defined content' // 默认值
    }
  },
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

需要注意的是: Vue 2.2.1 或更高版本中,inject注入的值会在props和data初始化之前得到

// 使用一个注入的值作为数据(data)的入口 或者 属性(props)的默认值
const Child = {
  inject: ['foo'],
  data () {
    return {
      bar: this.foo // 输出bar的值与foo相同
    }
  }
}

const Child = {
  inject: ['foo'],
  props: {
    bar: {
      default () {
        return this.foo
      }
    }
  }
}

-----------------------------------------------------------------------------------
// 注入可以通过设置默认值使其变成可选项
const Child = {
  inject: {
    foo: { // 注意: 此处key值必须是父组件中provide的属性的key
      from: 'bar', // 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol), data或者props中的可搜索值
      default: 'foo' // 属性是降级情况下使用的 value, 默认值为 ‘foo’
    }
  }
}

// 与 prop 的默认值类似
const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3] // 默认值为引用类型时,需要使用一个工厂方法返回对象
    }
  }
}

2. props(父传子) 

// 创建组件
Vue.component('props-demo-advanced', {
  props: {
    age: {
      type: Number,
      default: 0
    }
  }
})

// 父组件中注册和使用组件,并传值
<props-demo-advanced :age="age"> </props-demo-advanced>

3. $emit(子传父)

// 子组件Child, 负载payload可选
this.$emit('eventName', payload)

// 父组件 Parent
<Parent @evnetName="sayHi"></Parent>

4. eventBus(全局创建Vue实例)

                进行事件监听和数据传递。同时Vuex也是基于这个原理实现的 

// 三步使用
// 1. 创建
window.$bus = new Vue()

// 2. 注册事件
window.$bus.$on('user_task_change', (payload) => {
  console.log('事件触发')
})

// 3. 触发
window.$bus.$emit('user_task_change', payload)

5. vuex(状态管理) 

 目录结构如下

      其中vuex相关的三个文件counter.js、 index.js、 operate.js,内容如下:

 index.js

import Vue from 'vue'
import Vuex from 'vuex'
import counter from './counter.js'
import operate from './operate.js'
Vue.use(Vuex)

const state = {
  name: 'zhaoyh'
}

const getters = {
  getName (state) {
    return state.name
  }
}

const mutations = {
  changeName (state, payload) {
    state.name = `${state.name} ${payload}`
  }
}

const actions = {
  changeNameAsync (context, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        context.commit('changeName', payload)
      }, 1000)
    })
  }
}

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
    counter,
    operate
  }
})

export default store

counter.js

// 模块内方法调用本模块内的方法和数据
const state = {
  counterName: 'module > counter > zhaoyh'
}
const getters = {
  // state, getters: 本模块内的state, getters
  // rootState, rootGetters: 根模块/根节点内的state, getters
  // rootState, rootGetters: 同时也包括各个模块中的state 和 getters
  getCounterName (state, getters, rootState, rootGetters) {
    // rootState.name
    // rootState.counter.counterName
    // rootState.operate.operateName
    console.log(rootState)
    // rootGetters.getName,
    // rootGetters['counter/getCounterName']
    // rootGetters['operate/getOperateName']
    console.log(rootGetters)
    return state.counterName
  }
}
const mutations = {
  changeCounterName (state, payload) {
    state.counterName = `${state.counterName} ${payload}`
  }
}
const actions = {
  // context 与 store 实例具有相同方法和属性 ------  important!!!
  // context 包括: dispatch, commit, state, getters, rootState, rootGetters
  changeCounterNameAsync (context, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {context.commit('changeCounterName', payload)}, 1000)
    })
  }
}
export default {
  // 注意此处是namespaced,而不是namespace,
  // 写错的话程序不会报错,vuex静默无法正常执行
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

operate.js

// 模块内方法调用和获取本模块之外的方法和数据
const state = {
  operateName: 'module > operate > zhaoyh'
}
// 如果你希望使用全局 state 和 getter
// rootState 和 rootGetter 会作为第三和第四参数传入
// 也会通过 context 对象的属性传入 action
const getters = {
  // state, getters: 本模块内的state, getters
  // rootState, rootGetters: 根模块/根节点内的state, getters, 包括各个模块中的state 和 getters
  getOperateName (state, getters, rootState, rootGetters) {
    return state.operateName
  }
}
const mutations = {
  operateChangeCounterName (state, payload) {
    state.counterName = `${state.counterName} ${payload}`
  }
}
// 如果你希望使用全局 state 和 getter,
// 也会通过 context 对象的属性传入 action
const actions = {
  // context 与 store 实例具有相同方法和属性---- !!important!!!
  // context 包括:
  // dispatch, commit, state, getters, rootState, rootGetters
  operateChangeCounterNameAsync (context, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        /*
          * 若需要在全局命名空间内分发 action 或提交 mutation,
          * 将 {root: true} 作为第三参数传给 dispatch 或 commit 即可
        */
          context.commit('counter/changeCounterName', payload, { root: true })
          // 或 context.dispatch('counter/changeCounterNameAsync', null, { root: true })
      }, 1000)
    })
  }
}
export default {
  // 注意此处是namespaced,而不是namespace,
  // 写错的话程序不会报错,vuex静默无法正常执行
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

vuex属性、方法的引用方式

common-operate.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <ul>
      <li>name: {{name}}</li>
      <li>getName: {{getName}}</li>
      <li><button @click="changeName('lastName')">Mutation操作</button></li>
      <li><button @click="changeNameAsync('lastName')">Action操作</button></li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
export default {
  data () {
    return {
      title: 'vuex常规操作1'
    }
  },
  computed: {
    ...mapState(['name']),
    ...mapGetters(['getName'])
  },
  methods: {
    ...mapMutations(['changeName']),
    ...mapActions(['changeNameAsync'])
  }
}
</script>

<style scoped>
ul, li{
  padding: 0;
  margin: 0;
  padding: 8px 15px;
}
</style>

common-operate2.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <ul>
      <li>name: {{name}}</li>
      <li>getName: {{getName}}</li>
      <li><button @click="changeName('lastName')">Mutation操作</button></li>
      <li><button @click="changeNameAsync('lastName')">Action操作</button></li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'
export default {
  data () {
    return {
      title: 'vuex常规操作2'
    }
  },
  computed: {
    ...mapState(['name']),
    ...mapGetters(['getName'])
  },
  methods: {
    // mutation
    changeName () {
      this.$store.commit('changeName', 'lastName')
    },
    // actions
    changeNameAsync () {
      this.$store.dispatch('changeNameAsync', 'lastName')
    }
  }
}
</script>

<style scoped>
ul, li{
  padding: 0;
  margin: 0;
  padding: 8px 15px;
}
</style>

module-operate.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <ul>
      <li>name: {{counterName}}</li>
      <li>getName: {{getCounterName}}</li>
      <li><button @click="changeName('lastName')">Mutation操作</button></li>
      <li><button @click="changeNameAsync('lastName')">Action操作</button></li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'
export default {
  data () {
    return {
      title: '模块的基本操作方法'
    }
  },
  computed: {
    ...mapState('counter', ['counterName']),
    ...mapGetters('counter', ['getCounterName'])
  },
  methods: {
    // mutation
    changeName () {
      this.$store.commit('counter/changeCounterName', 'lastName')
    },
    // actions
    changeNameAsync () {
      this.$store.dispatch('counter/changeCounterNameAsync', 'lastName')
    }
  }
}
</script>

<style scoped>
ul, li{
  padding: 0;
  margin: 0;
  padding: 8px 15px;
}
</style>

module-operate2.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <ul>
      <li>name: {{counterName}}</li>
      <li>getName: {{getCounterName}}</li>
      <li><button @click="changeCounterName('lastName')">Mutation操作</button></li>
      <li><button @click="changeCounterNameAsync('lastName')">Action操作</button></li>
      <li><button @click="rename('rename')">Action操作</button></li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
  data () {
    return {
      title: '模块内方法调用本模块内内的方法和数据'
    }
  },
  computed: {
    ...mapState('counter', ['counterName']),
    ...mapGetters('counter', ['getCounterName'])
  },
  methods: {
    ...mapMutations('counter', ['changeCounterName']),
    ...mapActions('counter', ['changeCounterNameAsync']),
    // 多模块方法引入的方法名重命名
    ...mapActions('counter', {
      rename: 'changeCounterNameAsync'
    }),
    otherMethods () {
      console.log('继续添加其他方法。')
    }
  }
}
</script>

<style scoped>
ul, li{
  padding: 0;
  margin: 0;
  padding: 8px 15px;
}
</style>

module-operate3.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <ul>
      <li>name: {{counterName}}</li>
      <li>getName: {{getCounterName}}</li>
      <li><button @click="operateChangeCounterNameAsync('operate lastName')">Action操作</button></li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
  data () {
    return {
      title: '模块内方法调用和获取本模块之外的方法和数据'
    }
  },
  computed: {
    ...mapState('counter', ['counterName']),
    ...mapGetters('counter', ['getCounterName'])
  },
  methods: {
    ...mapMutations('operate', ['operateChangeCounterName']),
    ...mapActions('operate', ['operateChangeCounterNameAsync']),
    otherMethods () {
      console.log('继续添加其他方法。')
    }
  }
}
</script>

<style scoped>
ul, li{
  padding: 0;
  margin: 0;
  padding: 8px 15px;
}
</style>

6. $parent / $children / $refs (获取组件实例)

        $refs 获取对应组件实例,如果是原生dom,那么直接获取的是该dom
$parent / $children 该属性只针对vue组件,获取父/子组件实例
注: 节制地使用 $parent 和 $children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信

<!--  父组件,HelloWorld.vue-->
<template>
  <div class="hello">
    <ipc ref="ipcRef"></ipc>
  </div>
</template>

<script>
import ipc from './ipc'
export default {
  name: 'HelloWorld',
  data () {
    return {
      parentVal: 'parent content'
    }
  },
  mounted () {
    console.log(this.$refs.ipcRef.$data.child1) // "child1 content"
    console.log(this.$children[0].$data.child2) // "child2 content"
  },
  components: {
    ipc
  }
}
</script>
<!-- 子组件, ipc.vue-->
<template>
  <div>
  </div>
</template>

<script>
export default {
  props: {
  },
  data () {
    return {
      child1: 'child1 content',
      child2: 'child2 content'
    }
  },
  mounted () {
    console.log(this.$parent.parentVal) // "parent content"
  }
}
</script>

7. $attrs

       1) 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)
2) 当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外)
可以通过v-bind="$attrs" 将所有父作用域的绑定 (class、style、 ref 除外) 传入内部组件
注: 在创建高级别的组件时非常有用
// 根组件HelloWorld.vue 中引入 ipc.vue
koa=“ipcRef”
name=“go”
id=“id”
ref=“ref”
style=“border: 1px solid red;”
class=“className”

// ipc.vue 中引入 ipcChild.vue

<template>
  <div>
    <ipcChild v-bind="$attrs" selfDefine="selfDefine"></ipcChild>
  </div>
</template>

<script>
import ipcChild from './ipcChild'
export default {
  components: {
    ipcChild
  },
  mounted () {
    console.log(this.$attrs) // {id: "id", name: "go", koa: "ipcRef"}
  }
}
</script>

// ipcChild.vue中打印接收到的$attrs

<script>
export default {
  created () {
    console.log(this.$attrs) // "{"selfDefine":"selfDefine","koa":"ipcRef","name":"go","id":"id"}"
  }
}
</script>

8. $listeners

      包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
通过 v-on="$listeners" 将父作用域的时间监听器传入内部组件

       A、B、C三个组件依次嵌套, B嵌套在A中,C嵌套在B中。 借助 B 组件的中转,从上到下props依次传递,从下至上,$emit事件的传递,达到跨级组件通信的效果。$attrs以及$listeners 的出现解决的的问题,B 组件在其中传递props以及事件的过程中,不必在写多余的代码,仅仅是将 $attrs以及$listeners 向上或者向下传递即可。

9. Vue.observable

10. mixin

     mixin (混入) (谨慎使用全局混入, 如有必要可以设计插件并引入使用)
参见官方文档

  1. 一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项
  2. 当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
  3. 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
  4. 值为对象的选项,例如 methods、components 和
    directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。 

11. 路由传值 /引用数据类型值传递实现父子间数据的共享 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值