Vue3 数据通信
一、基本概念
数据在 vue 中是单向流动的,有利于管理数据状态和变化。
而在日常组件开发中,难以避免组件之间的数据通信。组件通信可以分为不同的场景,例如父子组件通信、兄弟组件通信、跨层级组件通信等。
Vue3 提供了多种方式进行组件间的通信:
- props: 自上而下传递数据
- emit: 自下而上报事件
- provide/inject: 跨层级传递数据
- v-model: 双向绑定
- expose/ref: 暴露组件实例的方法和数据
- pinia/vuex: 状态管理库
- eventBus/eitt: 全局事件总线
二、props/emit
props 用于父组件向子组件传递数据,子组件通过定义 props 来接收数据。数据流动是单向的,子组件只能接收数据,不能修改。
$emit 用于子组件向父组件发送事件通知,父组件可以根据这些事件执行相应的逻辑或更新状态。
<!-- 父组件 -->
<template>
<ChildComponent :message="parentMessage" @childClicked="handleChildClick" />
</template>
<script setup>
import ChildComponent from './childComponent.vue'
import { ref } from 'vue'
const parentMessage = ref('Hello from Parent Component!')
const handleChildClick = (val) => {
parentMessage.value = val
}
</script>
-------------------------------------------------------------------------------------------
<!-- 子组件 -->
<template>
<p>parentMessage: {{ props.message }}</p>
<button @click="notifyParent">Click Me</button>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
message: String
})
const emit = defineEmits(['childClicked'])
const notifyParent = (val) => {
val = 'Hello from Child Component!'
emit('childClicked', val)
}
</script>
子组件接收的变量要与父组件冒号后的变量一致,父组件 @ 后面的事件名要与子组件 emit 的事件名一致
三、provide/inject
依赖注入解决的问题是:有多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦。
provide 和 inject 就可以帮助我们解决这一问题。 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
<!-- 祖先组件 -->
<template>
<DescendantComponent />
</template>
<script setup>
import { provide } from 'vue'
import DescendantComponent from './descendantComponent.vue'
const ancestorValue = 'I provided from Ancestor Component!'
provide('sharedValue', ancestorValue)
</script>
--------------------------------------------------------------------------------------------
<!-- 后代组件 -->
<template>
<grandchildComponent />
</template>
<script setup>
import GrandchildComponent from './grandchildComponent.vue'
</script>
--------------------------------------------------------------------------------------------
<!-- 孙组件 -->
<template>
<p>祖先数据:{{ grandchildValue }}</p>
</template>
<script setup>
import { inject } from 'vue'
const grandchildValue = inject('sharedValue', 'default value')
</script>
inject 的第一个参数要和 provide 第一个参数一致,它第二个参数是可选的,即在没有匹配到 key 时使用的默认值。第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,必须将 true 作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。
四、v-model
v-model 用于在父子组件之间实现双向数据绑定,Vue3 支持多个 v-model 绑定。使用 v-model 的时候,在子组件中一定要使用update:XXX开头的,绑定的变量就是父组件v-model:后的变量
应用场景: 用于在组件之间进行双向数据绑定,如表单输入组件、计数器等。
<!-- 父组件 -->
<template>
<childComponent v-model:count="parentCount" />
<p>Parent Count: {{ parentCount }}</p>
</template>
<script setup>
import childComponent from './childComponent.vue'
import { ref } from 'vue'
const parentCount = ref(0)
</script>
-------------------------------------------------------------------------------------------
<!-- 子组件 -->
<template>
<button @click="increaseCount">Increase Count</button>
</template>
<script setup>
import { defineEmits, defineProps } from 'vue'
const props = defineProps(['count'])
const emit = defineEmits(['update:count'])
const increaseCount = () => {
emit('update:count', props.count + 1)
}
</script>
五、expose/ref
expose 和 ref 可以让子组件暴露某些方法或属性给父组件或其他组件访问,expose 用于定义子组件暴露的 API,ref 用于在父组件中引用子组件的实例并调用暴露的方法。
<!-- 父组件 -->
<template>
<button @click="callChildMethod">Call Child Method</button>
<ChildComponent ref="childRef" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './childComponent.vue'
const childRef = ref(null)
const callChildMethod = () => {
childRef.value?.doSomething()
}
</script>
--------------------------------------------------------------------------------------------
<!-- 子组件 -->
<template>
<button @click="doSomething">Do Something</button>
</template>
<script setup>
import { defineExpose } from 'vue'
const doSomething = () => {
alert('Doing something')
}
defineExpose({ doSomething })
</script>
六、vuex
vuex 是 vue 的官方状态管理库,用于集中管理应用的状态,并提供机制来更新状态,适用于大型应用中多个组件共享状态的场景。
应用场景: 适用于需要管理全局状态的应用,如用户信息、购物车内容等。
// store.js
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
count: 0
}
},
mutations: {
add(state) {
state.count++
},
sub(state) {
state.count--
}
},
actions: {
add({ commit }) {
commit('add')
},
sub({ commit }) {
commit('sub')
}
},
getters: {
getCount(state) {
return state.count
}
}
})
export default store
--------------------------------------------------------------------------------------------
<!-- 组件 add -->
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="add">+1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const count = computed(() => store.getters.getCount)
const add = () => store.dispatch('add')
</script>
--------------------------------------------------------------------------------------------
<!-- 组件 sub -->
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="sub">-1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const count = computed(() => store.getters.getCount)
const sub = () => store.dispatch('sub')
</script>
七、pinia
pinia 是 Vue3 的官方状态管理库,作为 vuex 的替代品,提供了一个更简洁的状态管理方案,支持 TypeScript,易于使用和扩展。
// countStore.js
import { defineStore } from 'pinia'
export const useCountStore = defineStore('count', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
}
})
-------------------------------------------------------------------------------------------
<!-- 组件 add -->
<template>
<div>当前count: {{ counter.count }}</div>
<button @click="counter.increment">+1</button>
</template>
<script setup>
import { useCountStore } from '@/stores/countStore'
const counter = useCountStore()
</script>
-------------------------------------------------------------------------------------------
<!-- 组件 sub -->
<template>
<div>当前count: {{ counter.count }}</div>
<button @click="counter.decrement">-1</button>
</template>
<script setup>
import { useCountStore } from '@/stores/countStore'
const counter = useCountStore()
</script>
八、eventBus
eventBus 是一种简单的事件发布/订阅模式,事件总线允许组件之间通过发布/订阅模式传递事件,但在大型应用中可能导致事件流动复杂,推荐使用其他状态管理工具。
在 Vue3 中,推荐使用 mitt 作为事件总线的实现,它提供了一个简单的 API 来处理事件总线。
应用场景: 适用于小型应用或临时的组件间通信需求。
// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()
--------------------------------------------------------------------------------------------
<!-- 组件 A -->
<template>
<button @click="sendMessage">Send Message to B</button>
</template>
<script setup>
import { emitter } from '../../eventBus'
const sendMessage = () => {
emitter.emit('message', 'Hello from Component A!')
}
</script>
--------------------------------------------------------------------------------------------
<!-- 组件 B -->
<template>
<p>当前message: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
import { emitter } from '../../eventBus'
const message = ref('默认信息')
emitter.on('message', (msg) => {
message.value = msg
})
</script>
发布信息方用 emit,接受信息方用 on