vue组件中的通信
一、组件间的关系
1、父子关系
2、兄弟关系
3、隔代关系
二、组件间的通信方式
方法一:props / $emit
// app.vue 父组件 <template> <div> <sub1 v-bind:data_from_app = "msg_parent"></sub1> </div> </template> <script> import sub1 from './components/sub1.vue'; export default { data(){ return { msg_parent:'父组件数据app' } }, components:{ sub1 } } </script> <style lang="scss" scoped> </style>
// 子组件 sub1.vue <template> <div> <p>{{data_from_app}}</p> </div> </template> <script> export default { data(){ return { sub_msg:'子组件sub1数据', } }, props:{ data_from_app:{ type:String, require:true } } } // 父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed </script> <style lang="scss" scoped> </style>
2、子组件向父组件传值---(通过事件的形式)--- $emit
子组件通过events给父组件发送消息,实际就是把自己的数据发送到父组件 $emit发送 $event接收
// 子组件 sub1.vue <template> <div> <p>{{data_from_app}}</p> <p @click="changeText">{{text_sub}}</p> </div> </template> <script> export default { data(){ return { sub_msg:'子组件sub1数据', text_sub:'子组件原来的数据' } }, props:{ data_from_app:{ type:String, require:true } }, methods:{ changeText(){ // 自定义事件,传递值 "子组件向父组件传值" this.$emit('titleChanged','子组件向父组件传值') } } } // 1、props // 父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed </script> <style lang="scss" scoped> </style>
// 父组件 app.vue <template> <div> <!-- 给子组件定义了一个点击事件changeText, 在子组件中的时间定义中,使用$emit传递titleChanged和要传递给父组件参数,子组件用$emit来传递参数数据 --> <sub1 v-on:titleChanged="updateText" v-bind:data_from_app = "msg_parent"></sub1> <!-- 父组件通过$event来接收传递过来的参数 updateText($event) --> <!-- 这里要与子组件 titleChanged 保持一致 --> </div> </template> <script> import sub1 from './components/sub1.vue'; export default { data(){ return { msg_parent:'父组件数据app', text:'父组件原本的text' } }, methods:{ updateText(e){ this.text = e; console.log(this.text) } }, components:{ sub1 } } </script> <style lang="scss" scoped> </style>
方法二、$emit / $on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量的实现了任何组件间的通信,包括父子、兄弟、隔代。当项目比较大时、可以考虑使用vuex。
实现方式:
var Event = new Vue() Event.$emit(事件名,数据) ; Event.$on(事件名,data => {});
<div id="itany"> <my-a></my-a> <my-b></my-b> <my-c></my-c> </div> <template id="a"> <div> <h3>A组件:{{name}}</h3> <button @click="send">将数据发送给C组件</button> </div> </template> <template id="b"> <div> <h3>B组件:{{age}}</h3> <button @click="send">将数组发送给C组件</button> </div> </template> <template id="c"> <div> <h3>C组件:{{name}},{{age}}</h3> </div> </template> <script> var Event = new Vue();//定义一个空的Vue实例 var A = { template: '#a', data() { return { name: 'tom' } }, methods: { send() { Event.$emit('data-a', this.name); } } } var B = { template: '#b', data() { return { age: 20 } }, methods: { send() { Event.$emit('data-b', this.age); } } } var C = { template: '#c', data() { return { name: '', age: "" } }, mounted() {//在模板编译完成后执行 Event.$on('data-a',name => { this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event }) Event.$on('data-b',age => { this.age = age; }) } } var vm = new Vue({ el: '#itany', components: { 'my-a': A, 'my-b': B, 'my-c': C } }); </script>
$on 监听了自定义事件data-a 和 data-b,因为有时不确定何时会触发事件,一般会在mounted或created钩子中监听
方法三、vuex
vuex 的原理:
vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中数据时,必须通过mutation进行,mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量同步操作需要走Action,但Action是无法直接修改state的,还是需要通过mutation来修改state的数据。最后根据state的变化,渲染到视图上。
各模块的介绍:
- Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块,由组件中的
$store.dispatch('action 名称', data1)
来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。 - commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法,由actions中的
commit('mutation 名称')
来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。 - state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
- getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。
vuex是vue的状态管理器,存储的数据是响应式的,但是并不会保存起来,刷新之后就回到了初始状态。具体做法应该是在vuex里面数据改变时把数据拷贝一份保存到localStorage中,刷新之后,如果localstorage里面有保存的数据,取出来替换store里面的state
方法四:$attrs / $listeners
$attrs:包含了父级作用域中,不被prop所识别(且获取)的特性绑定(class和style除外),当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(class和style除外),并且可以通过v-bind="$attrs"传入内部组件。通常配合interitAttrs选项一起使用。
$listeners:包含了父级作用域中(不含.native修饰器的)v-on事件监听器。他可以通过v-on="$listeners"传入内部组件
// app.vue 最外层的组件,本身定义了foo boo coo 三个数据 // 此时里面的子组件是 sub1 ,给它使用了 foo boo coo 三个数据,但 sub1 本身只定义了 foo 这个属性 <template> <div> <!-- 给子组件定义了一个点击事件changeText, 在子组件中的时间定义中,使用$emit传递titleChanged和要传递给父组件参数,子组件用$emit来传递参数数据 --> <p>父组件中的数据:foo:{{foo}}、boo:{{boo}}、coo:{{coo}}</p> <sub1 :foo="foo" :boo="boo" :coo="coo" v-on:titleChanged="updateText" v-bind:data_from_app = "msg_parent"></sub1> <!-- 父组件通过$event来接收传递过来的参数 updateText($event) --> <!-- 这里要与子组件 titleChanged 保持一致 --> <!-- <subvue1></subvue1> <subvue2></subvue2> --> </div> </template> <script> import sub1 from './components/sub1.vue'; // import subvue1 from './components/subVue/subvue1.vue'; // import subvue2 from './components/subVue/subvue2.vue'; export default { data(){ return { msg_parent:'父组件数据app', text:'父组件原本的text', foo:'html', boo:'css', coo:'vue' } }, methods:{ updateText(e){ this.text = e; console.log(this.text) } }, components:{ sub1, // subvue1, // subvue2 } } </script> <style lang="scss" scoped> </style>
// sub1 APP 的子组件,本身只定义了 foo 这个属性,因此通过$attrs拿到的是 boo coo // 这里sub1有了一个子组件 subvue1,给这个子组件使用了$attrs,即boo coo ,但本身子组件只有 boo 属性 <template> <div> <p>{{data_from_app}}</p> <p @click="changeText">{{text_sub}}</p> <p>foo:{{foo}}</p> <p>sub1的$attrs:{{$attrs}}</p> <subvue1 v-bind="$attrs"></subvue1> </div> </template> <script> import subvue1 from './subVue/subvue1.vue' export default { data(){ return { sub_msg:'子组件sub1数据', text_sub:'子组件原来的数据' } }, inheritAttrs:false, // 可已自动关闭挂载到根元素上没有在props声明的属性 props:{ data_from_app:{ type:String, require:true }, foo:String }, created(){ console.log(this.$attrs); }, methods:{ changeText(){ // 自定义事件,传递值 "子组件向父组件传值" this.$emit('titleChanged','子组件向父组件传值') } }, components:{ subvue1 } } // 1、props // 父组件通过props向下传递给子组件。注:组件中的数据共有三种形式:data,props,computed </script> <style lang="scss" scoped> </style>
// subvue1 sub1 的子组件,本身只定义了 boo 这一个属性,因此 attrs 拿到的是 coo <template> <div> <h3>subvue1组件</h3> <p>boo:{{boo}}</p> <!-- <button >subvue1组件:将数据发给组件subvue2</button> --> <p>subvue1的$attrs:{{$attrs}}</p> </div> </template> <script> import Vue from 'vue'; export default { data(){ return { data1:'subvue1', data_from:'' } }, props:{ boo:String }, mounted(){ }, created9(){ console.log(this.$attrs); }, methods:{ } } </script> <style lang="sass" scoped> </style>
小结:$attrs表示没有继承数据的对象,格式为{属性名:属性值},vue2.4提供了$attrs、$listeners来传递数据与实践,跨级组件之间的通信更加简单。
$attrs 里面存放的是父组件中绑定的非props属性。$listeners 里面存放的是父组件中绑定的非原生事件。
方法五:provide/inject
二者需要一起使用。允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在上下游关系成立的时间里始终生效。就是:祖先组件中通过provide来提供变量,然后在子孙组件中通过inject来注入变量。
主要解决了跨级组件中通信问题,不过他的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立一种主动提供与依赖注入的关系。
// A.vue export default { provide: { name: '浪里行舟' } }
// B.vue export default { inject: ['name'], mounted () { console.log(this.name); // 浪里行舟 } }
provide/inject绑定并不是响应式的,这是刻意为之的。如果传入一个可监听对象,那么其对象的属性还是可响应的。所以,A.vue中name改变了,B.vue的this.name是不会改变的。
方法六:$parent / $children 与 ref
ref :如果在普通的dom元素上使用,引用指向的就是dom元素;如果用在子组件上,引用就指向组件实例。
$parent / $children 访问父、子实例
以上两种方法得到的都是组件实例,使用后可以调用组件的方法或访问数据。
弊端:无法在跨级或兄弟间通信
// component-a 子组件 export default { data () { return { title: 'Vue.js' } }, methods: { sayHello () { window.alert('Hello'); } } }
// 父组件 <template> <component-a ref="comA"></component-a> </template> <script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 弹窗 } } </script>
四、总结
- 父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit
);通过父链 / 子链也可以通信($parent
/ $children
);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
- 兄弟通信:
Bus;Vuex
- 跨级通信:
Bus;Vuex;provide / inject API、$attrs/$listeners