原创不易,但愿能关注下咱们,再顺手点个赞~~ |
本文首发于政采云前端团队博客: Vue 组件数据通讯方案总结html
初识 Vue.js ,了解到组件是 Vue 的主要构成部分,但组件内部的做用域是相对独立的部分,组件之间的关系通常以下图:前端
组件 A 与组件 B 、C 之间是父子组件,组件 B 、C 之间是兄弟组件,而组件 A 、D 之间是隔代的关系。vue
那么对于这些不一样的关系,本文主要分享了他们之间能够采用的几种数据通讯方式,例如 Props 、$emit / $on 、Vuex 等,你们能够根据本身的使用场景能够选择适合的使用方式。vuex
一、Prop 是你能够在组件上注册的一些自定义特性。当一个值传递给一个 Prop 特性的时候,它就变成了那个组件实例的一个属性。父组件向子组件传值,经过绑定属性来向子组件传入数据,子组件经过 Props 属性获取对应数据api
// 父组件 <template> <div class="container"> <child :title="title"></child> </div> </template> <script> import Child from "./component/child.vue"; export default { name: "demo", data: function() { return { title: "我是父组件给的" }; }, components: { Child }, }; </script> 复制代码
// 子组件 <template> <div class="text">{{title}}</div> </template> <script> export default { name: 'demo', data: function() {}, props: { title: { type: String } }, }; </script> 复制代码
二、$emit 子组件向父组件传值(经过事件形式),子组件经过 $emit 事件向父组件发送消息,将本身的数据传递给父组件。数组
// 父组件 <template> <div class="container"> <div class="title">{{title}}</div> <child @changeTitle="parentTitle"></child> </div> </template> <script> import Child from "./component/child.vue"; export default { name: "demo", data: function() { return { title: null }; }, components: { Child }, methods: { parentTitle(e) { this.title = e; } } }; </script> 复制代码
// 子组件 <template> <div class="center"> <button @click="childTitle">我给父组件赋值</button> </div> </template> <script> export default { name: 'demo', data() { return { key: 1 }; }, methods: { childTitle() { this.$emit('changeTitle', `我给父组件的第${this.key}次`); this.key++; } } }; </script> 复制代码
小总结:经常使用的数据传输方式,父子间传递。缓存
这个方法是经过建立了一个空的 vue 实例,当作 $emit 事件的处理中心(事件总线),经过他来触发以及监听事件,方便的实现了任意组件间的通讯,包含父子,兄弟,隔代组件。性能优化
// 父组件 <template> <div class="container"> <child1 :Event="Event"></child1> <child2 :Event="Event"></child2> <child3 :Event="Event"></child3> </div> </template> <script> import Vue from "vue"; import Child1 from "./component/child1.vue"; import Child2 from "./component/child2.vue"; import Child3 from "./component/child3.vue"; const Event = new Vue(); export default { name: "demo", data: function() { return { Event: Event }; }, components: { Child1, Child2, Child3 }, }; </script> 复制代码
// 子组件1 <template> <div class="center"> 1.个人名字是:{{name}} <button @click="send">我给3组件赋值</button> </div> </template> <script> export default { name: "demo1", data() { return { name: "政采云" }; }, props: { Event: Object }, methods: { send() { this.Event.$emit("message-a", this.name); } } }; </script> 复制代码
// 子组件2 <template> <div class="center"> 2.个人年龄是:{{age}}岁 <button @click="send">我给3组件赋值</button> </div> </template> <script> /* eslint-disable */ export default { name: "demo2", data() { return { age: "3" }; }, props: { Event: Object }, methods: { send() { this.Event.$emit("message-b", this.age); } } }; </script> 复制代码
// 子组件3 <template> <div class="center">个人名字是{{name}},今年{{age}}岁</div> </template> <script> export default { name: 'demo3', data() { return { name: '', age: '' }; }, props: { Event: Object }, mounted() { this.Event.$on('message-a', name => { this.name = name; }); this.Event.$on('message-b', age => { this.age = age; }); }, }; </script> 复制代码
小总结:巧妙的在父子,兄弟,隔代组件中均可以互相数据通讯。bash
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。markdown
Vuex实现了一个单项数据流,经过建立一个全局的 State 数据,组件想要修改 State 数据只能经过 Mutation 来进行,例如页面上的操做想要修改 State 数据时,须要经过 Dispatch (触发 Action ),而 Action 也不能直接操做数据,还须要经过 Mutation 来修改 State 中数据,最后根据 State 中数据的变化,来渲染页面。
// index.js import Vue from 'vue'; import Tpl from './index.vue'; import store from './store'; new Vue({ store, render: h => h(Tpl), }).$mount('#app'); 复制代码
// store import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment(state) { state.count++; }, reduce(state) { state.count--; } }, actions: { actIncrement({ commit }) { commit('increment'); }, actReduce({ commit }) { commit('reduce'); } }, getters: { doubleCount: state => state.count*2 } }); export default store; 复制代码
// vue文件 <template> <div class="container"> <p>个人count:{{count}}</p> <p>doubleCount:{{doubleCount}}</p> <button @click="this.actIncrement">增长</button> <button @click="this.actReduce">减小</button> </div> </template> <script> import { mapGetters, mapActions, mapState } from "vuex"; export default { name: "demo", data: function() { return {}; }, components: {}, props: {}, computed: { ...mapState(["count"]), ...mapGetters(["doubleCount"]) }, methods: { ...mapActions(["actIncrement", "actReduce"]) } }; </script> 复制代码
Vuex 中须要注意的点:
Mutation :是修改State数据的惟一推荐方法,且只能进行同步操做。
Getter :Vuex 容许在Store中定义 "Getter"(相似于 Store 的计算属性)。Getter 的返回值会根据他的依赖进行缓存,只有依赖值发生了变化,才会从新计算。
本段只是简单介绍了一下 Vuex 的运行方式,更多功能例如 Module 模块请参考官网 。
小总结:统一的维护了一份共同的 State 数据,方便组件间共同调用。
Vue 组件间传输数据在 Vue2.4 版本后有了新方法。除了 Props 外,还有了 $attrs / $listeners。
Class
和 Style
除外)。当一个组件没有声明任何 Prop 时,这里会包含全部父做用域的绑定 (Class
和 Style
除外),而且能够经过 v-bind="$attrs"
传入内部组件——在建立高级别的组件时很是有用。下面来看个例子
// 父组件 <template> <div class="container"> <button style="backgroundColor:lightgray" @click="reduce">减dd</button> <child1 :aa="aa" :bb="bb" :cc="cc" :dd="dd" @reduce="reduce"></child1> </div> </template> <script> import Child1 from './component/child1.vue'; export default { name: 'demo', data: function() { return { aa: 1, bb: 2, cc: 3, dd: 100 }; }, components: { Child1 }, methods: { reduce() { this.dd--; } } }; </script> 复制代码
// 子组件1 <template> <div> <div class="center"> <p>aa:{{aa}}</p> <p>child1的$attrs:{{$attrs}}</p> <button @click="this.reduce1">组件1减dd</button> </div> <child2 v-bind="$attrs" v-on="$listeners"></child2> </div> </template> <script> import child2 from './child2.vue'; export default { name: 'demo1', data() { return {}; }, props: { aa: Number }, components: { child2 }, methods: { reduce1() { this.$emit('reduce'); } } }; </script> 复制代码
// 子组件2 <template> <div> <div class="center"> <p>bb:{{bb}}</p> <p>child2的$attrs:{{$attrs}}</p> <button @click="this.reduce2">组件2减dd</button> </div> <child3 v-bind="$attrs"></child3> </div> </template> <script> import child3 from './child3.vue'; export default { name: 'demo1', data() { return {}; }, props: { bb: Number }, components: { child3 }, methods: { reduce2() { this.$emit('reduce'); } } }; </script> 复制代码
// 子组件3 <template> <div class="center"> <p>child3的$attrs:{{$attrs}}</p> </div> </template> <script> export default { name: 'demo3', data() { return {}; }, props: { dd: String }, }; </script> 复制代码
简单来讲,$attrs 里存放的是父组件中绑定的非 props 属性,$listeners 里面存放的是父组件中绑定的非原生事件。
小总结:当传输数据、方法较多时,无需一一填写的小技巧。
Vue2.2 版本之后新增了这两个 API, 这对选项须要一块儿使用,以容许一个祖先组件向其全部子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。 简单来讲,就是父组件经过 Provider 传入变量,任意子孙组件经过 Inject 来拿到变量。
// 父组件 <template> <div class="container"> <button @click="this.changeName">我要更名字了</button> <p>个人名字:{{name}}</p> <child1></child1> </div> </template> <script> import Child1 from './component/child1.vue'; export default { name: 'demo', data: function() { return { name: '政采云' }; }, // provide() { // return { // name: this.name //这种绑定方式是不可响应的 // }; // }, provide() { return { obj: this }; }, components: { Child1 }, methods: { changeName() { this.name = '政采云前端'; } } }; </script> 复制代码
// 子组件 <template> <div> <div class="center"> <!-- <p>子组件名字:{{name}}</p> --> <p>子组件名字:{{this.obj.name}}</p> </div> <child2></child2> </div> </template> <script> import child2 from './child2.vue'; export default { name: 'demo1', data() { return {}; }, props: {}, // inject: ["name"], inject: { obj: { default: () => { return {}; } } }, components: { child2 }, }; </script> 复制代码
须要注意的是:Provide 和 Inject 绑定并非可响应的。这是刻意为之的。然而,若是你传入了一个可监听的对象,那么其对象的属性仍是可响应的。
因此,若是采用的是我代码中注释的方式,父级的 name 若是改变了,子组件this.name 是不会改变的,仍然是 政采云,而当采用代码中传入一个监听对象,修改对象中属性值,是能够监听到修改的。
Provider / Inject 在项目中须要有较多公共传参时使用仍是颇为方便的。
小总结:传输数据父级一次注入,子孙组件一块儿共享的方式。
this.$parent
访问父实例,子实例被推入父实例的 $children
数组中。ref
特性 的全部 DOM 元素和组件实例。ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;若是用在子组件上,引用就指向组件。// 父组件 <template> <div class="container"> <p>个人title:{{title}}</p> <p>个人name:{{name}}</p> <child1 ref="comp1"></child1> <child2 ref="comp2"></child2> </div> </template> <script> import Child1 from './component/child1.vue'; import Child2 from './component/child2.vue'; export default { name: 'demo', data: function() { return { title: null, name: null, content: '就是我' }; }, components: { Child1, Child2 }, mounted() { const comp1 = this.$refs.comp1; this.title = comp1.title; comp1.sayHello(); this.name = this.$children[1].title; }, }; </script> 复制代码
// 子组件1-ref方式 <template> <div> <div class="center">个人父组件是谁:{{content}}</div> </div> </template> <script> export default { name: 'demo1', data() { return { title: '我是子组件', content: null }; }, mounted() { this.content = this.$parent.content; }, methods: { sayHello() { window.alert('Hello'); } } }; </script> 复制代码
// 子组件2-children方式 <template> <div> <div class="center"></div> </div> </template> <script> export default { name: 'demo1', data() { return { title: '我是子组件2' }; }, }; </script> 复制代码
经过例子能够看到这两种方式均可以父子间通讯,而缺点也很统一,就是都不能跨级以及兄弟间通讯。
小总结:父子组件间共享数据以及方法的便捷实践之一。
组件间不一样的使用场景能够分为 3 类,对应的通讯方式以下:
你们能够根据本身的使用场景选择不一样的通讯方式,固然仍是都本身写写代码,试验一把来的印象深入喽。
招人,前端,隶属政采云前端大团队(ZooTeam),50 余个小伙伴正等你加入一块儿浪 [坏笑] 若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5年工做时间3年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手参与一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com