Vue的组件通信
在Vue应用中,组件通信是一个非常常见的需求。以下就是在Vue中比较常见的几种组价通信的方式
1. 常见的父子组件通信方式:props
实现思路:
vue
<!-- 父组件中: -->
<HelloWorld msg="Vite + Vue" />
<!-- 子组件中: -->
vue3:
const $props = defineProps({msg: String})
<span>子组件接收的:{{ $props.msg }}</span>
vue2:
props: {msg: {type: String}}
<span>子组件接收的:{{ msg }}</span>
解释
父组件给子组件传递的值可以是动态的变量,也可以是静态的字符串,在子组件中都可以通过props的形式去进行接收,对于vue2和vue3来说,只是接收的props方式不同而已
2. 自定义事件
实现思路:
vue
<!-- 父组件中: -->
<template>
<h1>这是子组件传给父组件的数据: {{ data }}</h1>
<HelloWorld @handleClick="getChildrenData" />
</template>
<script setup>
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
const data = ref(0)
const getChildrenData = (children) => {
data.value = children
}
</script>
<!-- 子组件中: -->
<template>
<div>
<button @click="onClick">点击count + 1</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(1)
const $emits = defineEmits(['handleClick'])
const onClick = () => {
$emits('handleClick', count.value ++ )
}
</script>
解释
自定义事件其实是在props的基础上进行了一些改变,props是通过传递的变量的方式,而自定义事件是通过将事件传递给子组件,然后再子组件适当的时机去触发这个事件,然后将数据进行传递
3. 祖先组件provide传递孙子组件inject接收
实现思路:
vue
<!-- 组件组件 -->
<template>
<div>
<Parent :data="Params" />
</div>
</template>
<script>
import Parent from '../../components/parent'
export default {
name: 'ancestor',
components: {
Parent
},
provide() {
return {
obj: this.obj
}
},
data() {
return {
Params: 2024
obj: {
set: 'hello world'
}
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<div class="head_box">
<Son :value="keep" />
<div>{{data}}</div>
</div>
</div>
</template>
<script>
import Son from '../son'
export default {
name: 'parent',
props: ['data']
components: {
Son
},
data() {
return {
keep: 'running'
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<div class="title_box">
<p>从爷组件中拿到的:{{ obj.set }}</p>
<p>从父组件中拿到的:{{ value}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'son',
inject: {
obj: {
from: 'Communication',
default: {}
}
},
props: ['value']
data() {
return {
}
}
}
</script>
解释
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,都会在其上下游组件关系成立的这段时间内生效。注意:provide 和 inject 绑定并不是可响应的,然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的
4. 父组件$refs获取子组件数据
实现思路:
vue
<!-- 父组件中 -->
<template>
<div>
<children ref="childrenRef" />
</div>
</template>
<script>
import children from './children.vue';
export default {
name: 'Parent',
components: {children},
data() {
return {
}
},
mounted() {
console.log(this.$refs.childrenRef.childData)
}
}
</script>
<!-- 子组件中 -->
<template>
<div>
<p>这是子组件的数据:{{ childData }}</p>
</div>
</template>
<script>
export default {
name: 'Children',
data() {
return {
childData: '我是子组件'
}
}
}
</script>
解释
在父组件中,引入子组件后可以给子组件打个ref,这样在父组件的实例上就能找到子组件的实例,就可以进而拿到子组件实例上的一些属性,可以是data中的数据,也可以调用子组件的方法
5. 子组件$parent获取父组件数据
实现思路:
vue
<!-- 父组件 -->
<template>
<div>
<children />
</div>
</template>
<script>
import children from './children.vue';
export default {
name: 'Parent',
components: {children},
data() {
return {
parentData: '这是父组件的数据'
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<p>这是我拿到的父组件的数据: {{ myData }}</p>
</div>
</template>
<script>
export default {
name: 'Children',
data() {
return {
myData: ''
}
},
mounted() {
this.myData = this.$parent.parentData
}
}
</script>
解释
在子组件的组件实例上,有parent属性,它对应的是组件的父组件的组件实例,我们可以通过$parent来获取父组件的实例,可以调用父组件的方法或者是拿到父组件data中的数据
6. 组件attrs和listeners透传
实现思路:
- $attrs
vue
<!-- 祖先组件中 -->
<template>
<div id="ancestor">
<Parent :value="value" num="3000" />
</div>
</template>
<script>
import Parent from '../parent'
export default {
name: 'ancestor',
components: {
Parent
},
data() {
return {
value: 2024
}
}
}
</script>
<!-- 父组件中 -->
<template>
<div id="parent">
<div class="head_box">
<Son v-bind="$attrs" />
</div>
</div>
</template>
<script>
import Son from '../son'
export default {
name: 'parent',
components: {
Son
},
data() {
return {}
}
}
</script>
<!-- 子组件中 -->
<template>
<div id="son">
<div class="title_box">
<p>爷爷的值:{{ $attrs.keys }}</p>
<p>爷爷的值:{{ $attrs.num }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'son',
data() {
return {}
}
}
</script>
- $listeners
vue
<template>
<div id="ancestor">
<Parent :value="value" @handleChildAdd="handleAdd" />
</div>
</template>
<script>
import Parent from '../parent'
export default {
name: 'ancestor',
components: {
Parent
},
data() {
return {
value: 1998
}
},
methods: {
// 父组件的事件
handleAdd() {
this.value++
}
}
}
</script>
<!-- 父组件中 -->
<template>
<div id="parent">
<div class="head_box">
<Son v-bind="$attrs" v-on="$listeners" />
</div>
</div>
</template>
<script>
import Son from '../son'
export default {
name: 'parent',
components: {
Son
},
data() {
return {}
}
}
</script>
<!-- 子组件中 -->
<template>
<div id="son">
<div class="title_box">
<p>爷爷的值:{{ $attrs.value }}</p>
<span @click="chlidEvent">点击触发爷爷的事件</span>
</div>
</div>
</template>
<script>
export default {
name: 'son',
data() {
return {}
},
methods: {
chlidEvent() {
// 通过$emit调用爷爷的方法
this.$emit('handleChildAdd')
}
}
}
</script>
7. 事件总线event-bus
实现思路:
vue
<!-- 在main.js中 -->
<script>
// main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this;
}
}).$mount('#app')
</script>
<!-- a组件中 -->
<template>
<div>
A <input v-model="msg" /> <button @click="sendData">传递</button>
</div>
</template>
<script>
export default {
data() {
return {
msg: ""
}
},
methods: {
sendData() {
this.$bus.$emit('updateData', this.msg)
}
}
};
</script>
<!-- b组件中 -->
<template>
<div>
B: {{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: ''
};
},
methods:{
update(msg){
this.message = msg
}
},
mounted(){
this.$bus.$on('updateData',this.update)
},
}
</script>
解释
- 为什么要在beforeCreate函数中去绑定$bus? 因为main.js是最先执行的,然后是app.vue组件,再之后就是各个模块的组件执行,在这个生命周期函数中进行添加事件总线的话,我们就可以在模块组件创建时就能直接使用总线去触发或者绑定事件
- 事件总线的好处就是不再拘束于父子组件,爷孙组件传值了,兄弟组件之间也可以传数据
- 我们在a组件中添加一个按钮,当按钮被点击的时候就会触发updateData事件,将我们的msg数据传递过去,然后再b组件中,定义了一个message属性,然后再挂载的时候,就绑定了updateData事件,然后将update方法作为被监听的函数,当a组件触发事件的时候,b组件就会调用update方法去接收a组件传递过来的数据
8. VueX
实现思路:
vue
<!-- 在App.vue中 -->
<script>
async getEnumSourceByGray() {
try {
const result = await this.$API.common.getEnumResource.get()
if(result.errno === 0) {
this.$store.commit('SET_enum', result.data)
}
} catch (error) {
console.log(error)
}
}
</script>
<!-- 在store中 -->
<script>
export default {
state: {
enum: {}
},
mutations: {
SET_enum(state, key) {
state.enum = key
}
}
}
</script>
<!-- 在想使用的组件中 -->
<script>
const {xxx} = this.$store.global.enum
// 然后我们就可以去使用了
</script>
解释
VueX可以理解为是一个仓库,是用来存储我们想使用的公共数据,随着我们App的挂载而添加
9. Vue-Router
实现思路:
vue
<!-- 在一个组件中 -->
<template>
<button @click="goAndTakeData">点击跳转并携带数据</button>
</template>
<script>
export default {
data() {
return {
takeData: '携带的数据'
}
},
methods: {
goAndTakeData() {
this.$router.push({
path: '/detail',
query: {
something: this.takeData
}
})
}
}
}
</script>
<!-- 在另一个组件中 -->
<template>
<div>接收到的数据: {{ acceptData }}</div>
</template>
<script>
export default {
data() {
return {
acceptData: ''
}
},
mounted() {
const {something} = this.$route.query
this.accpetData = something
}
}
</script>
解释
这是一个比较简单的从路由跳转中去携带数据并获取数据,比较适合去传递一些比较简单的数据,比如id、类型等等,如果需要的数据比较庞大且复杂,我们建议选择别的方式
10. 浏览器的本地存储
实现思路:
vue
<!-- 在一个组件中 -->
<template>
<button @click="setData">点击存储数据/button>
</template>
<script>
export default {
data() {
return {
name: '数据'
}
},
methods: {
setData() {
window.localStorage.setItem('SETDATA', this.name)
}
}
}
</script>
<!-- 在另一个组件中 -->
<template>
<p>这是拿到的数据: {{ getName }}</p>
</template>
<script>
export default {
data() {
return {
getName: ''
}
},
mounted() {
this.getName = window.localStorage.getItem('SETDATA')
}
}
</script>
解释
对于浏览器的存储传递的数据,我们需要注意的是,在浏览器中进行存储的是字符串类型的,如果我们需要存储对象或者数据,需要将他们转换成JSON字符串的形式,其中:浏览器的存储也分为两类,一个是localSTorage,一个是sessionStorage,这两个的存储方式不太相同。 localSTorage是本地存储,当存储到浏览器中后,就不会随着浏览器的关闭而销毁,且存储大小有限,我们需要再适当的时机使用localStorage.clear去清空 sessionStorage是会话存储,会随着当前会话的关闭而清除。