最近使用vue来开发的场景比较多, 常常会遇到父子间通信问题, 所以想大体的总结一下, 和大家一起探讨遇到的问题.
组件的通讯经常用到以下两种
- 父组件向子组件传递数据
- 子组件向父组件传递数据
1.父组件向子组件传递数据
通过prop传递
通过prop传递的数据类型可以是String,Number,Boolean,Object, Array 和 Function, 也可以是表达式
在父组件中, 用v-bind给子组件绑定数据
<template> <div class="father-page"> <h1>我是父组件</h1> <child :string="str" :number="num" :boolean="boo" :object="obj" :array="arr" :expression="obj.a+obj.b" :childfunc="func"></child> </div> </template> <script> import child from './components/child.vue'; export default { data () { return { str: '父组件的msg', num: 0, arr: [1, 2, 3, 4], boo: true, obj: { a: 1, b: 2 }, } }, methods: { func () { this.num++ console.log(this.num) } }, components: {child} } </script> <style lang="less"> .father-page { background-color: aliceblue; position: fixed; top: 0; bottom: 0; left: 0; right: 0; } h1, h2 { text-align: center; } p { text-align: center; } </style>
在子组件中, 用prop注册传进来的值
<template> <div class="child-page"> <h2>我是子组件</h2> <p>string: {{string}}</p> <p>number: {{number}}</p> <p v-if="boolean">boolean: {{boolean}}</p> <p>object: {{object.a}}</p> <p>expression: {{expression}}</p> <p>function: <button @click="childfunc">点我吧</button></p> </div> </template> <script> export default { props: { string: { type: String }, number: { type: Number }, boolean: { type: Boolean }, object: { type: Object }, array: { type: Array }, childfunc: { type: Function }, expression: { require: false } } } </script> <style scoped lang="less"> .child-page { background-color: antiquewhite; button { display: inline-block; width: 50px; height: 20px; background-color: cadetblue; color: white; } } </style>
prop传值需要注意的是
- vue提倡单项数据流
父组件通过 props 向下传递数据给子组件, 而子组件不能直接修改prop中的数据, 否则会报以下错误:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value.
如果子组件想要修改父组件的值, 可以使用下面介绍的子组件向父组件传递数据的方法. - 子组件的props参数值是异步更新
请看下面的例子
父组件中:
<template> <div class="father-page"> <h1>我是父组件</h1> <p><button @click="func">点我吧</button></p> <child :string="str" ref="child"></child> </div> </template> <script> import child from './components/child.vue'; export default { data () { return { str: 'hello world', } }, methods: { func () { this.str = 'hi, world!' this.$refs.child.childfunc() } }, components: {child} } </script>
子组件中:
<template> <div class="child-page"> <h2>我是子组件</h2> <p>父组件的值: {{string}}</p> </div> </template> <script> export default { props: { string: { type: String }, }, methods: { childfunc () { console.log(this.string) } } } </script>
点击父组件的button, 首先改变父组件的str值, 然后调用子组件中的childfunc方法, 出乎意料的是子组件的方法console.log出来的结果是’hello world’, 而不是改变后的str值’hi, world!’. 原因就是子组件的props参数值是异步更新的,所以在调用子组件methods里的childfunc方法时, 子组件的prop还未更新. 所以我们在子组件methods里使用props参数时要注意参数值是否是最新值。我们可以采用以下两种方法来保证子组件获得最新的props的值.
① 使用$nextTick,在数据变化之后等待 Vue 完成更新 DOM.
比如上方的例子里父组件可以更改为:
methods: { func () { this.str = 'hi, world!' this.$nextTick(()=>{ this.$refs.child.childfunc() }) } },
② 子组件使用watch监听prop参数变化
使用的方法根据场景而定.
2.子组件向父组件传递数据
1) 通过v-on 和 $emit 实现
父组件
<template> <div class="father-page"> <h1>我是父组件</h1> <p><button @click="reduce">减少</button></p> <p>total: {{total}}</p> <child v-model="total"></child> </div> </template> <script> import child from './components/child.vue'; export default { data () { return { total: 0 } }, methods: { reduce () { this.total-- } }, components: {child} } </script>
子组件
<template> <div class="child-page"> <h2>我是子组件</h2> <p><button @click="add">添加</button></p> <p>total: {{parentValue}}</p> <input type="text" :value="parentValue" @input="handleInput" > </div> </template> <script> export default { props: ['parentValue'], model: { prop: 'parentValue', event: 'change' }, methods: { add () { this.$emit('change', this.parentValue + 1) }, handleInput (e) { this.$emit('change', e.target.value) } } } </script>
从上面的例子可以看出, 子组件不直接修改父组件的数据, 而是通过$emit绑定事件名称, 触发父组件的事件,在父组件的方法中完成修改.
2) 使用ref
父组件通过在子组件添加ref属性,然后可以通过ref属性名称获取到子组件的实例。
父组件
<child :string="str" ref="child"></child> <!-- 称获取到子组件的实例 --> this.$refs.child
同样的, 子组件也可以使用this.$parent获取到父组件的实例, 但是这样的方法不被推荐, 因为这更属于一种主动的查找, 而不是数据的传递.
3.父子组件v-model实现双向绑定
v-model不仅能够用在input上, 也可以使用在组件上.只需在子组件的model下添加prop和event:
- prop 通过v-model传的值来更新prop的值
- event 通过将新变化的prop的值更新给v-model的数据
父组件
<template> <div class="father-page"> <h1>我是父组件</h1> <button @click="reduce">减少</button> <p>total: {{total}}</p> <child v-model="total"></child> </div> </template> <script> import child from './components/child.vue'; export default { data () { return { total: 0 } }, methods: { reduce () { this.total-- } }, components: {child} } </script>
子组件
<template> <div class="child-page"> <h2>我是子组件</h2> <button @click="add">添加</button> <p>total: {{parentValue}}</p> input: <input type="text" :value="parentValue" @input="handleInput" > </div> </template> <script> export default { props: ['parentValue'], model: { prop: 'parentValue', event: 'change' }, methods: { add () { this.$emit('change', this.parentValue + 1) }, handleInput (e) { this.$emit('change', e.target.value) } } } </script>
v-model 在组件中使用相当于一个语法糖, 它的功能可以被v-bind, v-on以及emit来代替, 子组件通过v-bind接受父组件的值, 再通过v-on和emit来代替,子组件通过v−bind接受父组件的值,再通过v−on和emit来改变父组件的值.总而言之, 数据是自上往下(父传子)的, 事件是自下往上(子传父)的.
实现组件之间通讯的方法还有很多, 比如给Vue实例绑定一个事件总线, 或者使用vuex来统一管理.不同场景使用不同方法.