【Vue2】组件传值的六种方法

Vue 组件之间的通信大概归类为:

  • 父子组件通信: props/$emit;ref/refs;$attrs / $listeners;$parent / $children
  • 兄弟组件通信: eventBus;vuex
  • 跨级通信: eventBus;Vuex;$attrs / $listeners

一、props/$emit(父子组件通信)

1.父组件向子组件传值

通过 props 传值。
父组件的代码:

<template>
  <div class="section">
    <child :list="list"></child>
  </div>
</template>
<script>
import child from './child.vue'
export default {
  components: { child },
  data() {
    return {
      list: ['a1', 'b2', 'c3']
    }
  }
}
</script>

子组件的代码(即 child.vue):

<template>
  <div>
    <span v-for="(item, index) in list" :key="index">{{item}}</span>
  </div>
</template>
<script>
export default {
  props: ['list'],
}
</script>

2.子组件向父组件传值

$emit 绑定一个自定义事件, 当这个语句被执行时, 就会将参数 arg 传递给父组件,父组件通过 v-on 监听并接收参数。

父组件的代码:

<template>
  <div class="section">
    <child :list="list"  @onEmitIndex="onEmitIndex"></child>
    <p>{{item}} => {{currentIndex}}</p>
  </div>
</template>
<script>
import child from './child.vue'
export default {
  components: { child },
  data() {
    return {
      currentIndex: -1,
      item: '',
      list: ['a1', 'b2', 'c3']
    }
  },
  methods: {
    onEmitIndex(arg) {
      this.currentIndex = arg.index;
      this.item = arg.item;
    }
  }
}
</script>

子组件的代码(即 child.vue):

<template>
  <div>
    <button v-for="(item, index) in list" :key="index" @click="emitIndex(index, item)">{{item}}</button>
  </div>
</template>
<script>
export default {
  props: ['list'],
  methods: {
    emitIndex(index, item) {
      this.$emit('onEmitIndex', {index, item})
    }
  }
}
</script>

二、ref/refs(父子组件通信)

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

父组件 parent.vue:

<template>
  <div>
    <button @click="sayHello">sayHello</button>
    <child ref="childForRef"></child>
  </div>
</template>
<script>
import child from './child.vue'
  export default {
    components: { child },
    data () {
      return {
        childForRef: null,
      }
    },
    mounted() {
      this.childForRef = this.$refs.childForRef;
      console.log(this.childForRef.name);
      // this.childForRef.sayHello();
    },
    methods: {
      sayHello() {
        this.childForRef.sayHello()
      }
    }
  }
</script>

子组件 child.vue:

<template>
  <div>child 的内容</div>
</template>
<script>
export default {
  data () {
    return {
      name: '我是 child',
    }
  },
  methods: {
    sayHello () {
      console.log('hello');
      alert('hello');
    }
  }
}
</script>

三、eventBus(兄弟组件通信、跨级通信)

eventBus 又称为事件总线,在 vue 中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。一般用来兄弟组件和隔代组件传值。

  1. 首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它。
    bus.js:
import Vue from 'vue'
export const bus = new Vue()
  1. 发送事件,假设有 child1、child2 两个兄弟组件,在 child1.vue 中使用bus.$emit()发送事件。
    parent.vue:
<template>
  <div>
    <child1></child1>
    <child2></child2>
  </div>
</template>
<script>
import child1 from './child1.vue'
import child2 from './child2.vue'
export default {
  components: { child1, child2 }
}
</script>

child1.vue:

<template>
  <div>
    <button @click="additionHandle">+加法器</button>    
  </div>
</template>
<script>
import {bus} from '@/bus.js'
// console.log(bus)
export default {
  data(){
    return{
      num:1
    }
  },
  methods:{
    additionHandle(){
      bus.$emit('addition', {
        num:this.num++
      })
    }
  }
}
</script>
  1. child2.vue 中使用bus.$on()接收事件。
<template>
  <div>计算和: <br>child1Num => {{child1Num}}<br>count + child1Num => {{count}}</div>
</template>
<script>
import { bus } from '@/bus.js'
export default {
  data() {
    return {
      child1Num: 0,
      count: 0,
    }
  },
  mounted() {
    bus.$on('addition', arg=> {
      this.child1Num = arg.num;
      this.count = this.count + arg.num;
    })
  }
}
</script>

4.如果想移除事件的监听, 可以像下面这样操作:

 import { bus } from './bus.js'
bus.$off('addition', {})

四、Vuex(兄弟组件通信、跨级通信)

父组件:

<template>
  <div id="app">
    <ChildA/>
    <ChildB/>
  </div>
</template>

<script>
  import ChildA from './ChildA' // 导入A组件
  import ChildB from './ChildB' // 导入B组件

  export default {
    name: 'App',
    components: {ChildA, ChildB} // 注册A、B组件
  }
</script>

子组件childA:

<template>
 <div id="childA">
   <h1>我是A组件</h1>
   <button @click="transform">点我让B组件接收到数据</button>
   <p>因为你点了B,所以我的信息发生了变化:{{BMessage}}</p>
 </div>
</template>

<script>
 export default {
   data() {
     return {
       AMessage: 'Hello,B组件,我是A组件'
     }
   },
   computed: {
     BMessage() {
       // 这里存储从store里获取的B组件的数据
       return this.$store.state.BMsg
     }
   },
   methods: {
     transform() {
       // 触发receiveAMsg,将A组件的数据存放到store里去
       this.$store.commit('receiveAMsg', {
         AMsg: this.AMessage
       })
     }
   }
 }
</script>

子组件 childB:

<template>
 <div id="childB">
   <h1>我是B组件</h1>
   <button @click="transform">点我让A组件接收到数据</button>
   <p>因为你点了A,所以我的信息发生了变化:{{AMessage}}</p>
 </div>
</template>

<script>
 export default {
   data() {
     return {
       BMessage: 'Hello,A组件,我是B组件'
     }
   },
   computed: {
     AMessage() {
       // 这里存储从store里获取的A组件的数据
       return this.$store.state.AMsg
     }
   },
   methods: {
     transform() {
       // 触发receiveBMsg,将B组件的数据存放到store里去
       this.$store.commit('receiveBMsg', {
         BMsg: this.BMessage
       })
     }
   }
 }
</script>

vuex的 store.js

import Vue from 'vue'
 import Vuex from 'vuex'
 Vue.use(Vuex)
 const state = {
   // 初始化A和B组件的数据,等待获取
   AMsg: '',
   BMsg: ''
 }
 
 const mutations = {
   receiveAMsg(state, payload) {
     // 将A组件的数据存放于state
     state.AMsg = payload.AMsg
   },
   receiveBMsg(state, payload) {
     // 将B组件的数据存放于state
     state.BMsg = payload.BMsg
   }
 }
 
 export default new Vuex.Store({
   state,
   mutations
 })

然后需要在 main.js 中引入 vuex

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store';

Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

五、$attrs/$listeners(父子组件通信、跨级通信)

parent.vue:

<template>
  <div>
    <child-a :name="name" :age="age" :gender="gender" :height="height" title="嘿嘿嘿"></child-a>
  </div>
</template>
<script>
import ChildA from './ChildA'
export default {
  components: { ChildA },
  data() {
    return {
      name: "zhang",
      age: "18",
      gender: "女",
      height: "158"
    };
  }
};
</script>

childA.vue

<template>
 <div>
   <p>props 接收到的 name: {{ name}}</p>
   <p>childA 的 $attrs: {{$attrs}}</p>
   <child-b v-bind="$attrs"></child-b>
 </div>
</template>
<script>
const childB = () => import("./childB.vue");
export default {
 components: {
   childB
 },
 inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
 props: {
   name: String // name作为props属性绑定
 },
 created() {
   console.log(this.$attrs);
    // { "age": "18", "gender": "女", "height": "158", "title": "嘿嘿嘿" }
 }
};
</script>

childB.vue

<template>
 <div class="border">
   <p>props 接收到的 age: {{age}}</p>
   <p>childB 的 $attrs: {{$attrs}}</p>
 </div>
</template>
<script>

export default {
 inheritAttrs: false,
 props: {
   age: String
 },
 created() {
   console.log(this.$attrs); 
   // { "gender": "女", "height": "158", "title": "嘿嘿嘿" }
 }
};
</script>
<style scoped>
 .border{border: 1px solid #000;}
</style>

六、$children/$parent(父子组件通信)

parent.vue:

 <template>
  <div>
    <div>{{msg}}</div>
    <child></child>
    <button @click="changeA">点击改变子组件值</button>
  </div>
</template>

<script>
import Child from './child'
export default {
  components: { Child },
  data() {
    return {
      msg: 'Welcome'
    }
  },

  methods: {
    changeA() {
      this.$children[0].messageA = 'this is new value'
    }
  }
}
</script>

child.vue:

<template>
  <div class="com_a">
    <span>{{messageA}}</span>
    <p>获取父组件的值为:  {{parentVal}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messageA: 'this is old'
    }
  },
  computed:{
    parentVal(){
      return this.$parent.msg;
    }
  }
}
</script>

要注意边界情况,如在 #app 上拿 $parent 得到的是 new Vue() 的实例,在这实例上再拿 $parent 得到的是 undefined,而在最底层的子组件拿 $children 是个空数组。也要注意得到 $parent$children 的值不一样,$children 的值是数组,而 $parent 是个对象。

代码下载:https://github.com/windliang/simpleJS/tree/master/vue/componentCommunication

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335