Vue组件间通信的几种方式

0、前言

组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:

种类:

  1. 父子组件通信;
  2. 兄弟组件通信;
  3. 跨级组件通信

在这里插入图片描述

1、props/$emit(父子)

父组件通过 props 向子组件传递数据,在子组件中就可以用this.xxx方式使用,子组件通过 $emit 和父组件通信。

  1. props的特点:

● props只能是父组件向子组件进行传值,props使得父子组件之间形成一个单向的下行绑定。子组件的数据会随着父组件的更新而响应式更新。
● props可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以是传递一个函数。
● props属性名规则:若在props中使用驼峰形式,模板中标签需要使用短横线的形式来书写。

  1. $emit的特点:

● $emit 绑定一个自定义事件,当这个事件被执行的时候就会将参数传递给父组件,而父组件通过v-on监听并接收参数。

// Parent.vue 组件
<template>
  <div>
    父组件
    <child :msg="msg" @changeTitle="updateTitle"></child>
    <h2>{
    
    {
    
     parentTitle }}</h2>
  </div>
</template>

<script>

import Child from './Child.vue'
export default {
    
    
  name: 'Parent',
  components: {
    
     Child },
  data () {
    
    
    return {
    
    
      msg: 'hello',
      parentTitle: ''
    }
  },
  methods: {
    
    
    updateTitle (e) {
    
    
      this.parentTitle = e
    }
  },
};

</script>

<style scoped></style>
// Child.vue 组件
<template>
  <div>
    子组件
    {
    
    {
    
     msg }}
    <button @click="handleChildClick">子组件按钮</button>
  </div>
</template>

<script>

export default {
    
    
  name: 'Child',
  props: {
    
    
    msg: {
    
    
      typeof: 'String',
      required: true
    }
  },
  data () {
    
    
    return {
    
    
      childTitle: '我是子组件传递过来的值'
    }
  },
  methods: {
    
    
    handleChildClick () {
    
    
      this.$emit('changeTitle', this.childTitle)
    }
  },
};

</script>

<style scoped></style>

2、ref / $refs(父子)

可实现父子组件之间的通信,常用于父组件中调用子组件的方法或者获取子组件的属性。

// Parent.vue 组件
<template>
  <div>
    父组件
    <child ref="child"></child>

  </div>
</template>

<script>

import Child from './Child.vue'
export default {
    
    
  name: 'Parent',
  components: {
    
     Child },
  data () {
    
    
    return {
    
    

    }
  },
  mounted () {
    
    
    const child = this.$refs.child
    console.log(child.name)
    child.changeName('调用子组件')
  },
};

</script>


<style scoped></style>
// Child.vue 组件
<template>
  <div>
    子组件
  </div>
</template>

<script>

export default {
    
    
  name: 'Child',
  data () {
    
    
    return {
    
    
      name: '我是子组件',
    }
  },
  methods: {
    
    
    changeName (msg) {
    
    
      console.log(msg)
    }
  },
};

</script>


<style scoped></style>

3、provide / inject(深度父子)

依赖注入,常见于插件或者组件库里。
多个组件嵌套时,顶层组件provide提供变量,后代组件都可以通过inject来注入变量。
缺点:传递的数据不是响应式的,inject接收到数据后,provide中的数据改变,但是后代组件中的数据不会改变。所以建议传一些常量或者方法。

// Parent.vue 组件
<template>
  <div>
    父组件
    <child ref="child"></child>

  </div>
</template>

<script>

import Child from './Child.vue'
export default {
    
    
  name: 'Parent',
  components: {
    
     Child },
  data () {
    
    
    return {
    
    

    }
  },
  // 方式1 不能获取methods中对的方法
  provide: {
    
    
    name: '我是父组件的值'
  }
  // 方式2 不能获取data中的属性
  provide () {
    
    
    return {
    
    
      name: '刘德华',
      childMethod: this.changeTitle
    }
  },
  methods: {
    
    
    changeTitle () {
    
    
      console.log('这是注册的方法')
    }
  },
};

</script>


<style scoped></style>
// Child.vue 组件
<template>
  <div>
    子组件
  </div>
</template>

<script>

export default {
    
    
  name: 'Child',
  data () {
    
    
    return {
    
    

    }
  },
  inject: ['name', 'childMethod'],
  mounted () {
    
    
    console.log(this.name)
    this.childMethod()
  }

};

</script>


<style scoped></style>

4、EventBus 事件总线 (任意两个组件通讯)

$emit 去监听,用$on去触发,注意需要$off来取消监听,否则可能会造成内存泄漏。
对于比较小型的项目,没有必要引入 vuex 的情况下,可以使用 eventBus。相比我们上面说的所有通信方式,eventBus 可以实现任意两个组件间的通信。

// 方法1: 抽离出一个单独的js文件 Bus.js,然后在需要的地方引入
import Vue from 'vue'
export default new Vue()

// 方法2:直接挂载到全局main.js中
import Vue from 'vue'
Vue.prototype.$bus = new Vue()

// 方法3:注入到Vue跟对象上 main.js
import Vue from 'vue'
new Vue({
    
    
	el: "#app",
	data: {
    
    
		Bus: new Vue()
	}
})

// Parent.vue 组件
<template>
  <div>
    <child></child>
    <button @click="handleClick">按钮</button>
  </div>
</template>

<script>
import Bus from './Bus.js'
import Child from './Child.vue'
export default {
    
    
  name: 'Parent',
  components: {
    
     Child },
  data () {
    
    
    return {
    
    

    }
  },
  methods: {
    
    
    handleClick () {
    
    
      // 自定义事件名
      Bus.$emit("sendMsg", "发送的内容")
    }
  },
};

</script>


<style scoped></style>

// Child.vue 组件
<template>
  <div>
  </div>
</template>

<script>
import Bus from './Bus.js'
export default {
    
    
  name: 'Child',
  data () {
    
    
    return {
    
    

    }
  },

  mounted () {
    
    
    // 监听事件的触发
    Bus.$on('sendMsg', data => {
    
    
      console.log('这是接受到的数据', data)
    })
  },
  beforeDestroy () {
    
    
    // 取消监听
    Bus.$off("sendMsg")
  }

};

</script>

<style scoped></style>

5、$attrs / $listener(深度父子)

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。

// Parent.vue组件:
<template>
  <div>
    <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
    
    
  name: 'Parent',
  components: {
    
     Child },
  data () {
    
    
    return {
    
    
      name: '张三',
      age: 30,
      infoObj: {
    
    
        from: '浙江',
        job: 'policeman',
        hobby: ['reading', 'writing', 'skating']
      }
    }
  },
  methods: {
    
    
    updateInfo () {
    
    
      console.log('update info')
    },
    delInfo () {
    
    
      console.log('delete info')
    }
  }
};

</script>

<style scoped></style>
// Child.vue 组件
<template>
  <!-- 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件 -->
  <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" />
</template>
<script>
import GrandSon from './GrandSon.vue'
export default {
    
    
  name: 'Child',
  components: {
    
     GrandSon },
  props: ['name'],
  data () {
    
    
    return {
    
    
      height: '180cm',
      weight: '72kg'
    }
  },
  created () {
    
    
    console.log(this.$attrs)
    // 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
    console.log(this.$listeners) // updateInfo: f, delInfo: f
  },
  methods: {
    
    
    addInfo () {
    
    
      console.log('add info')
    }
  }
}
</script>
// GrandSon.vue 组件
<template>
  <div>
    {
    
    {
    
     $attrs }} --- {
    
    {
    
     $listeners }}
  </div>
</template>
<script>
export default {
    
    
  props: ['weight'],
  created () {
    
    
    console.log(this.$attrs) // age, infoObj, height 
    console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
    this.$emit('updateInfo') // 可以触发 father 组件中的updateInfo函数
  }
}
</script>

6、$parent / $children

// Parent.vue 组件
<template>
  <div class="hello_world">
    <div>{
    
    {
    
     msg }}</div>
    <child></child>
    <button @click="changeA">点击改变子组件值</button>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
    
    
  name: 'Parent',
  components: {
    
     Child },
  data () {
    
    
    return {
    
    
      msg: 'hello'
    }
  },
  methods: {
    
    
    changeA () {
    
    
      // 获取到子组件A
      console.log(this.$children)
      this.$children[0].messageA = 'this is new value'
    }
  }

};

</script>

<style scoped></style>
// Child.vue 组件
<template>
  <div class="com_a">
    <span>{
    
    {
    
     messageA }}</span>
    <p>获取父组件的值为: {
    
    {
    
     parentVal }}</p>
  </div>
</template>
<script>

export default {
    
    
  name: 'Child',
  data () {
    
    
    return {
    
    
      messageA: 'this is old'
    }
  },
  computed: {
    
    
    parentVal () {
    
    
      return this.$parent.msg
    }
  }

}
</script>

7、vuex

7.1 Vuex原理

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便实现组件数据之间的共享;Vuex集中管理共享的数据,易于开发和后期维护;能够高效的实现组件之间的数据共享,提高开发效率;存储在Vuex的数据是响应式的,能够实时保持页面和数据的同步。
在这里插入图片描述

7.2 Vuex各模块功能

Vuex重要核心属性包括:state,mutations,action,getters,modules。
Vuex主要包括以下几个核心模块:

  1. State:用于数据的存储,是store中的唯一数据源;
  2. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
  3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
  4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
  5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

7.3 Vuex与localStorage

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

let defaultCity = "杭州"
try {
    
       // 用户关闭了本地存储功能,此时在外层加个try...catch
  if (!defaultCity){
    
    
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))//在Vuex里保存的状态都是数组,而localStorage只支持字符串,所以需要用JSON转换
  }
}catch(e){
    
    }
export default new Vuex.Store({
    
    
  state: {
    
    
    city: defaultCity
  },
  mutations: {
    
    
    changeCity(state, city) {
    
    
      state.city = city
      try {
    
    
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 数据改变的时候把数据拷贝一份保存到localStorage里面
      } catch (e) {
    
    }
    }
  }
})

这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:

JSON.stringify(state.subscribeList);   // array -> string
JSON.parse(window.localStorage.getItem("subscribeList"));    // string -> array 

总结

根据以上对这7种组件间的通信方法,可以将不同组件间的通信分为4种类型:父子组件间通信、跨代组件间通信、兄弟组件间通信、任意组件间通信

  1. 父子组件间通信
    子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。
    通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组件,这样也可以实现通信。
    使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。
  2. 跨代组件间通信
    跨代组件间通信其实就是多层的父子组件通信,同样可以使用上述父子组件间通信的方法,只不过需要多层通信会比较麻烦。
    使用上述的7种方法的$attrs / $listeners方法。
  3. 兄弟组件间通信
    通过 $parent + $refs 以父组件为中间人来获取到兄弟组件,也可以进行通信。
  4. 任意组件间通信
    使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。
    如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

猜你喜欢

转载自blog.csdn.net/DZQ1223/article/details/131415376