“组件化、模块化、工程化”是现在前端中不可回避的话题,在Vue开发中体现的尤为明显,组件与组件之间的通信也有多种类型,参考下图:

一、父组件向子组件通信
1.1.props传递数据
如上图中父组件向子组件通信使用props
传递数据,在组件中使用props
选项来声明需要从父级接收的数据,props值有两种:一种是字符串数组,一种是对象。
1 2 3 4
| <div id="root"> <input type="text" v-model="parentMessge" /> <my-component :parent-data="parentMessage"></my-component> </div>
|
1 2 3 4 5 6 7 8 9 10
| Vue.component('my-component', { props: ['parentData'], template: '<div>来自父组件的数据:{{parentData}}</div>' }) new Vue({ el: '#root', data: { parentMessage: '' } })
|
父组件my-component
绑定了一个数据parentData
,数据来源于input
的输入值,组件内的template
里包含一个div
,并显示相应的数据。
1.2.单向数据流
Vue2.x和Vue1.x比较大的一个改变是Vue2.x通过props
传递的数据是单项流的,也就是说父级props
的更新会向下流到子组件中,但是反过来不行,这样尽可能的解耦父子组件,避免子组件意外修改父组件的状态,从而
导致你的应用的数据流难以理解,详见官文单项数据流。
1 2 3 4
| <div id="app"> <p>父组件数据:{{pData}}</p> <single-component :init-count="pData"></single-component> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Vue.component('single-component', { props: ['initCount'], template: '<div>{{count}}<button @click="handleClick">+1</button></div>', data: function(){ return { count: this.initCount } }, methods: { handleClick: function(){ this.count++ } } }); new Vue({ el: '#app1', data: { pData: 3 } })
|
子组件中声明一个本地data数据count
来保存父组件传递过来的数据,之后就与父组件的数据无关,不管count
如何修改,都不会影响父组件的数据,当我们点击+1按钮对count
执行+1操作,此时父组件数据并不受影响。
二、子组件向父组件通信
2.1.自定义事件
当子组件要向父组件传递数据时,可以用到v-on
进行自定义事件,子组件通过$emit()
来触发事件,父组件通过$on()
来监听事件。
1 2 3 4 5 6 7
| <div id="root"> <p>总数: {{ total }}</p> <my-component @increase="handleGetTotal" @reduce="handleGetTotal" ></my-component> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Vue.component('my-component', { template: '<div>' + '<button @click="handleIncrease">+1</button>' + '<button @click="handleReduce">-1</button>' + '</div>', data: function(){ return { counter: 0 } }, methods: { handleIncrease: function(){ this.counter++; this.$emit('increase', this.counter); }, handleReduce: function(){ this.counter--; this.$emit('reduce', this.counter); } } });
new Vue({ el: '#root', data: { total: 0 }, methods: { handleGetTotal: function(total){ this.total = total; } } })
|
这里在组件的template
的两个按钮自定义handleIncrease
和handleReduce
事件执行对组件数据的增减,然后通过this.$emit
触发increase
和reduce
事件并传递组件中的数据,在父组件中通过@increase
和@reduce
监听事件,并执行相应的操作。
2.2.自定义组件使用v-model
Vue2.x可以在自定义组件上使用v-model
,实际上是@input
事件的一个语法糖。
1 2 3 4 5 6
| <div id="app"> <p>总数:{{total}}</p> <model-component v-model="total"></model-component> <p>总数:{{total1}}</p> <model-component @input="handleGetTotal"></model-component> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| Vue.component('model-component',{ template: '<button @click="handleClick">+1</button>', data: function(){ return { counter: 0 } }, methods: { handleClick: function(){ this.counter++; this.$emit('input', this.counter); } } });
new Vue({ el: '#app', data: { total: 0, total1: 0 }, methods: { handleGetTotal: function(total){ this.total1 = total; } } });
|
这里自定义了一个组件model-component
,引用了两次,第一次使用v-model
指令绑定total数据,第二次使用@input
事件绑定handleGetTotal
方法;而在组边template
的按钮里使用@click
绑定handleClick
,在方法里面对数据执行操作并触发input
事件,传递了处理后的数据。
三、非父子组件间的通信
3.1.借助中央事件总线bus
中央处理总线就是一个全局的空的vue
实例,用于承载组件间通信的操作,从单词意思也可以看出。
1 2 3 4 5 6
| <div id="app"> <p>实例下显示孙组件的内容:{{message}}</p> <wrap-component> <in-component></in-component> </wrap-component> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var bus = new Vue();
var Child = { template: '<div><h6>这是内层孙组件</h6><button @click="handleEvent">孙按钮派发事件</button></div>', methods: { handleEvent: function(){ bus.$emit('on-message', '来自孙组件的内容'); } } }
Vue.component('wrap-component',{ template: '<div><slot></slot>这是外层组件</div>', components: { 'in-component': Child } });
new Vue({ el: '#app', data: { message: '' }, components: { 'in-component': Child }, mounted: function(){ var _this = this; bus.$on('on-message', function(msg){ _this.message = msg; }) } })
|
这里首先创建了一个名为bus的Vue空实例,用js对象的形式创建了一个组件,并在wrap-component
组件中注册,由于局部注册的组件在其子组件中不可用,所以在根实例中再注册一次。局部组件通过按钮事件向bus触发一个’on-message’事件,传递相应的数据,并在app vue实例下监听相应的事件。
3.2.父链和子链
在子组件中,可以通过this.$parent
访问该组件的父实例和组件,同样的可以通过this.$children
访问它的子组件,可以无限上下递归,直到最内层的组件或根实例。
1 2 3 4
| <div id="app"> <p>通过父链修改的数据:{{message}}</p> <component-a></component-a> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Vue.component('component-a',{ template: '<button @click="handleEvent">通过父链直接修改数据</button>', methods: { handleEvent: function(){ this.$parent.message = '来自组件component-a的内容'; } } });
new Vue({ el: '#app', data: { message: '默认数据,点击按钮被修改' } });
|
这里直接通过组件的按钮修改了父实例的数据。
3.3.子组件索引
当子组件过多时,多次使用$children
显然不可取,这时候可以通过ref
属性给组件一个索引名称。
1 2 3 4
| <div id="app"> <button @click="handleRef">通过ref获取子组件实例</button> <component-b ref="comA"></component-b> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Vue.component('component-b', { template: '<div>子组件</div>', data: function(){ return { message: '子组件内容' } } });
new Vue({ el: '#app', methods: { handleRef: function(){ var msg = this.$refs.comA.message; console.log(msg); } } })
|
这里this.$refs.comA
获取到指定索引的组件,注意的是,$refs只在组件渲染完成后才填充,并且它是非响应式的,应该避免在模板和计算属性中使用。