在vue3中我们可以用很多方式来实现父与子、子与父、兄弟与兄弟等不同组件的通信。接下来我会展示常用的几种通信方式以及他们各自的特点和使用细节。
props传递
props是最常见的,父组件在调用子组件时通过绑定变量向子组件传递数据。
javascript
// parent.vue
<template>
<Child :msg="message" :count="count" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const message = ref('Hello from Parent')
const count = ref(0)
</script>
javascript
// child.vue
<template>
<div>
<p>消息: {{ msg }}</p>
<p>数量: {{ count }}</p>
</div>
</template>
<script setup>
defineProps({
msg: String,
count: Number
})
</script>
特点:只能父向子传递配置、数据、状态等,不能子向父传递。
使用细节:需要在defineProps里进行变量的声明才能使用。直接解构props没有响应式,需要用toRefs()
自定义事件
自定义事件(defineEmits)的方式可以实现子向父组件传递数据,自定义事件有很多变式写法,此处只演示最简单的使用方法,便于理解中心思想。
javascript
// parent.js
<template>
<p>{{ message }}</p>
<Child @updateMsg=update />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const message = ref('Hello from Parent')
const update = (val) => {
message.value = val
}
</script>
javascript
// child.js
<template>
<div>
<input :value @update=update></input>
</div>
</template>
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['update-msg'])
const msg = ref('Hello from child')
const update = (e) => {
msg.value = e.target.value
emit('update-msg', msg.value)
}
</script>
特点:自定义事件是通过调用defineEmits显式声明抛出一个父组件监听的事件来实现更新父组件自身的变量。
使用细节:vue3废除了 <math xmlns="http://www.w3.org/1998/Math/MathML"> o n 和 on和 </math>on和emit, 转而用defineEmits来实现自定义事件。 在使用时需要先在调用子组件时声明自定义事件名才能在子组件中进行抛出。
provide和inject
provide和inject适用于祖先组件往后代组件传递数据,可以避免层层props传递。
javascript
// parent.js
<template>
<p>{{ message }}</p>
<Child @updateMsg=update />
</template>
<script setup>
import { provide, ref } from 'vue'
import Child from './Child.vue'
const message = ref('Hello from Parent')
provide('message', message)
</script>
javascript
// child.js
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
特点:provide/inject可以不用层层传递,在祖先组件进行一次provide声明后续的后代组件都可以通过inject进行调用。
注意事项:在vue3中provide和inject在使用前都需要进行import引入, provide上传的变量如果是用的ref声明,那么子组件更新provide,父组件inject的变量值也会更新。
vuex
vue3不建议使用vuex,但是vuex的作为很常见的设计思想还是有必要进行了解。
使用vuex需要先新建一个store文件夹,然后在文件夹下新建一个index.js文件作为store初始化文件
javascript
// store/index.js
import {createStore } from 'vuex'
const selfStore = createStore({
state() { //变量声明
return {
message: 'hello vuex'
}
},
mutations: { // 实际上执行变量操作的部分,该部分是同步的
UPDATE_MESSAGE(state, payload){
state.message = payload
}
},
actions: { //也能实现对变量的操作,本质上是通过commit来借助
//mutations来实现对变量的操作,是异步的
updatedMessage({commit}, payload){
commit('UPDATE_MESSAGE', payload)
}
},
getters: { //变量的getter接口,通过调用getters可以获得store内部变量
message: state => state.message
}
})
export default selfStore
然后在main.js文件中通过app.use()挂载store
javascript
import { createApp } from 'vue'
import selfStore from '../src/store'
import parent from '../src/components/communication/parent.vue'
import child from '../src/components/communication/child.vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.use(selfStore)
app
.component('parent', parent)
.component('child', child)
app.mount('#app')
在父子组件中分别进行调用store实现子组件向父组件通讯效果
javascript
// parent.js
<template>
<p>{{ message }}</p>
<Child />
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex'
import Child from './Child.vue'
const store = useStore()
const message = computed(() => store.getters.message)
</script>
javascript
// child.js
<template>
<div>
<input :value=msg @change=changeMsg />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex'
const msg = ref('this is child')
const store = useStore()
const changeMsg = (e) => {
msg.value = e.target.value
store.dispatch('updatedMessage', e.target.value)
}
</script>
特点:vuex也是经过一次声明就可以在多个组件中进行调用,不用层层传入。在声明store时就已经声明了变量的初始化、修改、获取等所有行为,在组件使用时不用重新声明。
注意事项:在使用vuex时我们需要手动引入createStore和useStore。vuex的设计思想是单一数据源,但是也可以设置多个store,只是需要在一个js文件里进行声明。在更新数据时建议使用actions来更新数据。
在调用数据是需要用computed对store的变量进行响应式赋值,如果直接进行赋值不通过computed,那么变量不具备响应式。
pinia
pinia是vux推荐的store管理库,和vuex不同的是pinia的设计理念就是为了实现多store情况。
在创建vue3项目时会自动创建一个stores文件夹,我们所有的store文件都放入其中进行管理。
pinia的store有两种写法,一种是传统的声明state、actions、getters的选项式写法,一种是省略state、actions、getters的组合式写法,本文中演示更简单的组合式。
javascript
// stores/counter.js
import {defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
然后在main.js文件中通过app.use()挂载pinia
javascript
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import parent from '../src/components/communication/parent.vue'
import child from '../src/components/communication/child.vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.use(createPinia())
app
.component('parent', parent)
.component('child', child)
app.mount('#app')
在父子组件中分别进行调用pinia实现子组件向父组件通讯效果
javascript
// parent.js
<template>
<p>{{ counterStore.doubleCount }}</p>
<Child />
</template>
<script setup>
import { ref } from 'vue'
import { useCounterStore } from '../../stores/counter'
import Child from './Child.vue'
const counterStore = useCounterStore()
</script>
javascript
// child.js
<template>
<div>
<button @click="buttonClick">{{ counterStore.doubleCount }}</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useCounterStore } from '../../stores/counter'
const buttonClick = () => {
counterStore.increment()
}
</script>
特点: pinia支持多个store,每个store单独存放在一个js文件里并通过export进行导出,在需要使用时可以直接通过import进行导入就可以进行使用。和vuex不同的是,pinia的store由pinia直接进行管理,
注意事项:在使用pinia时要注意选项式和组合式语法不能同时在一个store中进行使用。pinia自身的state就具备响应式但如果将state里的变量赋给组件中的变量,也需要进行computed修饰使其具有响应式。