vue 组件通信方式全面详解
众所周知, Vue 主要思想就是组件化开发. 因为, 在实际的项目开发中, 肯定会以组件的开发模式进行. 形如页面和页面之间需要通信一样, Vue 组件和组件之间肯定也需要互通有无, 共享状态. 接下来, 我们就悉数给大家展示所有 Vue 组件之间的通信方式.
组件关系
App 组件和 A 组件, A 组件和 B 组件, B 组件和 C 组件形成父子关系
B 组件和 D 组件形成兄弟关系
App 组件和 C 组件, App 和 B 组件形成了隔代关系 (其中的层级可能是多级, 既隔多代)
组件通信
这么多的组件关系, 那么组件和组件之间又有哪些通信的方式呢? 各种方式的区别又是什么? 适用场景又是什么呢?
props 和 $emit
这种方式是我们日常开发中应用最多的一种方式.
props 以单向数据流的形式可以很好的完成父子组件的通信
所谓单向数据流: 就是数据只能通过 props 由父组件流向子组件, 而子组件并不能通过修改 props 传过来的数据修改父组件的相应状态. 至于为什么这样做, Vue 官网做出了解释:
** 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定: 父级 prop 的更新会向下流动到子组件中, 但是反过来则不行. 这样会防止从子组件意外改变父级组件的状态, 从而导致你的应用的数据流向难以理解.
额外的, 每次父级组件发生更新时, 子组件中所有的 prop 都将会刷新为最新的值. 这意味着你不应该在一个子组件内部改变 prop. 如果你这样做了, Vue 会在浏览器的控制台中发出警告.
正因为这个特性, 于是就有了对应的 $emit.$emit 用来触发当前实例上的事件. 对此, 我们可以在父组件自定义一个处理接受变化状态的逻辑, 然后在子组件中如若相关的状态改变时, 就触发父组件的逻辑处理事件.
- let Child = {
- template: `<div>
- <input type="text" v-model='msg'/>
- <button @click='handleClick'> 传递 </button>
- </div>`,
- props: ['msg'],
- methods: {
- handleClick() {
- this.$emit('getChildData', '子组件数据')
- }
- },
- }
- let Parent = {
- data() {
- return {
- msg: '小马哥',
- val:''
- }
- },
- methods: {
- getChildData(val) {
- this.val = val;
- }
- },
- template: `
- <div>
- <p > 我是一个父组件 </p>
- <p > 我是 {{val}}</p>
- <Child :msg='msg' @getChildData='getChildData'></Child>
- </div>
- `,
- components: {
- Child
- }
- }
- let vm = new Vue({
- el: '#app',
- template: `
- <div>
- <Parent></Parent>
- </div>
- `,
- components: {
- Parent
- }
- })
父传子: 父组件传递 msg 数据给子组件, 通过 v-bind 绑定 msg, 子组件中直接可以用 props 接收绑定的数据
子传父: 子组件触发相应的事件, 通过 $emit 触发事件传递数据, 父组件中绑定对应的事件, 通过 $on 监听对应的事件 接收子组件传递的数据
EventBus - 中央事件总线
如果想实现兄弟组件之间进行通信, 在项目规模不大的情况下, 完全可以使用中央事件总线 EventBus 的方式. 如果你的项目规模是大中型的, 那我们会使用 vuex 状态管理
EventBus 通过新建一个 Vue 事件 bus 对象, 通过 bus.$emit 触发事件, bus.$on 监听触发的事件.
- Vue.component('A', {
- template: `
- <div>
- <p > 我是 A 组件 </p>
- <button @click='handleClick'>A 传递到 B</button>
- </div>
- `,
- data() {
- return {
- msg: 'hello 小马哥'
- }
- },
- methods: {
- handleClick() {
- this.$bus.$emit('globalEvent',this.msg);
- }
- },
- })
- Vue.component('B', {
- template: `
- <div>
- <p > 我是 B 组件 </p>
- <h3>{{aValue}}</h3>
- </div>
- `,
- data() {
- return {
- aValue: ''
- }
- },
- created () {
- this.$bus.$on('globalEvent',(val)=>{
- this.aValue = val;
- })
- },
- })
- // 定义中央事件总线
- let bus = new Vue();
- // 将中央事件总线赋值给 Vue.prototype 中, 这样所有组件都能访问到了
- Vue.prototype.$bus = bus;
- let vm = new Vue({
- el: '#app',
- template: `
- <div>
- <A></A>
- <B></B>
- </div>
- `,
- })
$attrs 和 $listeners
通过 props 进行组件通信的方式只适合直接的父子组件, 如果父组件 A 下面有子组件 B, 组件 B 下面有组件 C, 这时如果组件 A 直接想传递数据给组件 C 那就行不通了! 只能是组件 A 通过 props 将数据传给组件 B, 然后组件 B 获取到组件 A 传递过来的数据后再通过 props 将数据传给组件 C. 当然这种方式是非常复杂的, 无关组件中的逻辑业务一种增多了, 代码维护也没变得困难, 再加上如果嵌套的层级越多逻辑也复杂, 无关代码越多!
针对这样一个问题, Vue 2.4 提供了 $attrs 和 $listeners 来实现能够直接让组件 A 传递消息给组件 C
- Vue.component('A', {
- template: `
- <div>
- <p > 我是 A 组件 </p>
- <B :msg='msg' @getCData='getCData'></B>
- </div>
- `,
- methods: {
- getCData(val) {
- alert(val)
- }
- },
- data() {
- return {
- msg: 'hello 小马哥'
- }
- },
- })
- Vue.component('B', {
- template: `
- <div>
- <p > 我是 B 组件 </p>
- <!-- C 组件中能直接触发 getCData 的原因在于: B 组件调用 C 组件时, 使用 v-on 绑定了 $listeners 属性 -->
- <!-- 通过 v-bind 绑定 $attrs 属性, C 组件可以直接获取到 A 组件中传递下来的 props(除了 B 组件中 props 声明的) -->
- <C v-bind='$attrs' v-on='$listeners'></C>
- </div>
- `,
- // props: ['msg'],
- data() {
- return {
- }
- }
- })
- Vue.component('C', {
- template: `
- <div>
- <p > 我是 C 组件 </p>
- <p>{{$attrs.msg}}</p>
- <button @click='handleClick'> 传递数据 </button>
- </div>
- `,
- methods: {
- handleClick() {
- this.$emit('getCData', 'C 组件的数据')
- }
- },
- data() {
- return {
- }
- }
- })
- let vm = new Vue({
- el: '#app',
- template: `
- <div>
- <A></A>
- </div>
- `,
- })
$attrs: 包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外). 当一个组件没有声明任何 prop 时, 这里会包含所有父作用域的绑定属性 (class 和 style 除外), 并且可以通过 v-bind="$attrs" 传入内部组件.
$listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器. 它可以通过 v-on="$listeners" 传入内部组件.
provide 和 inject
在父组件中通过 provider 来提供属性, 然后在子组件中通过 inject 来注入变量. 不论子组件有多深, 只要调用了 inject 那么就可以注入在 provider 中提供的数据, 而不是局限于只能从当前父组件的 prop 属性来获取数据, 只要在父组件的生命周期内, 子组件都可以调用. 这和 React 中的 Context API 有没有很相似!
- Vue.component('A', {
- template: `
- <div>
- <p > 我是 A 组件 </p>
- <B></B>
- </div>
- `,
- provide:{
- a:"祖先 A 的数据"
- },
- data() {
- return {
- msg: 'hello 小马哥'
- }
- },
- })
- Vue.component('B', {
- template: `
- <div>
- <p > 我是 B 组件 </p>
- <C></C>
- </div>
- `,
- data() {
- return {
- }
- }
- })
- Vue.component('C', {
- template: `
- <div>
- <p > 我是 C 组件 </p>
- <h3>{{a}}</h3>
- </div>
- `,
- inject:['a'],
- data() {
- return {
- }
- }
- })
- let vm = new Vue({
- el: '#app',
- template: `
- <div>
- <A></A>
- </div>
- `,
- })
在 parent 组件中, 通过 provide 属性, 以对象的形式向子孙组件暴露了一些属性
在 child 组件中, 通过 inject 属性注入了 parent 组件提供的数据, 实际这些通过 inject 注入的属性是挂载到 Vue 实例上的, 所以在组件内部可以通过 this 来访问
注意: 官网文档提及 provide 和 inject 主要为高阶插件 / 组件库提供用例, 并不推荐直接用于应用程序代码中.
$parent 和 $children
这里要说的这种方式就比较直观了, 直接操作父子组件的实例.$parent 就是父组件的实例对象, 而 $children 就是当前实例的直接子组件实例了, 不过这个属性值是数组类型的, 且并不保证顺序, 也不是响应式的.
- Vue.component('Parent', {
- template: `
- <div>
- <p > 我是父组件 </p>
- {{msg}}
- <hr/>
- <Child></Child>
- </div>
- `,
- mounted () {
- // 读取子组件数据, 注意 $children 并不保证顺序, 也不是响应式的
- console.log(this.$children[0].a)
- },
- data() {
- return {
- msg: 'hello 小马哥'
- }
- },
- })
- Vue.component('Child', {
- template: `
- <div>
- <p > 我是孩子组件 </p>
- <input type="text" @input='changeValue'/>
- <h2>{{myMsg}}</h2>
- </div>
- `,
- methods: {
- changeValue() {
- this.$parent.msg = '子组件中的数据'
- }
- },
- data() {
- return {
- myMsg:this.$parent.msg,
- a:"小马哥"
- }
- }
- })
- let vm = new Vue({
- el: '#app',
- template: `
- <div>
- <Parent></Parent>
- </div>
- `,
- })
Vuex 状态管理
Vuex 是状态管理工具, 实现了项目状态的集中式管理. 工具的实现借鉴了 Flux https://facebook.github.io/flux/docs/overview.html ,Redux http://redux.js.org/ , 和 The Elm Architecture https://guide.elm-lang.org/architecture/ 的模式和概念. 当然与其他模式不同的是, Vuex 是专门为 vue.js 设计的状态管理库, 以利用 Vue.JS 的细粒度数据响应机制来进行高效的状态更新. 详细的关于 Vuex 的介绍, 你既可以去查看官网文档 https://vuex.vuejs.org/zh/ , 也可以查看本专栏关于 Vuex 一系列的介绍.
来源: https://www.cnblogs.com/majj/p/12617802.html