Vue3 组件的数据传输
方法目录
- 方式一:defineProps(父传子)
- 方式二:defineEmits(子传父)
- 方式三:v-model(父子双向绑定)
- 方式四:Pinia
- 方式五:provide/inject(依赖注入, 适合祖孙组件通信 )
- 方式六:mitt库
方式一:父传子(props)
在子组件中可以使用defineProps接收父组件向子组件的传值
父组件
xml
<template>
<child-component :message="parentMessage" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
let parentMessage = ref('111')
</script>
子组件
xml
<template>
<div>{{ message }}</div>
</template>
<script setup>
defineProps(['message'])
</script>
方式二、子传父(emits)
父组件: 定义一个函数,参数为子组件中传递的数据,自定义事件名,并将函数传给子组件。
xml
<template>
<child-component @send-data="getSonData"/>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
// 定义一个函数,用于接收子组件中的数据
function getSonData(data){
console.log("子组件中的数据:",data)
}
</script>
子组件: defineEmits声明事件,调用父组件中声明的事件回调函数,并把数据当做参数传入。emit函数第一个参数为事件回调函数名,后续参数为该回调函数的参数。
xml
<template>
<button @click="emit('send-data',data)">把数据发送到父组件</button>
</template>
<script setup>
import { ref } from 'vue'
let data = ref('111') // 子组件中的数据
const emit = defineEmits(['send-data']) // 接收props
</script>
方式三、父子组件进行双向绑定(v-model)
在子组件中可以使用defineModel与父组件进行双向绑定。注意:defineModel在vue3.3版本实验性使用,稳定版在vue3.4。
父组件
xml
<template>
<div class="father">
{{ a }}
<childPage v-model="a" />
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import childPage from "./childPage.vue";
const a = ref<number>();
</script>
子组件
xml
<template>
<div class="childPage">
<button @click="updateA">按钮</button>
</div>
</template>
<script lang="ts" setup>
import { defineModel } from "vue";
const a = defineModel({
type: Number,
default: 0,
});
const updateA = () => {
a.value += 1;
};
</script>
方式四、使用 Pinia 进行组件通信
场景 1:跨组件共享状态(如用户登录信息)
(1)定义Store
typescript
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'Anonymous',
isLogin: false
}),
actions: {
login(name: string) {
this.name = name
this.isLogin = true
},
logout() {
this.name = 'Anonymous'
this.isLogin = false
}
}
})
(2) 组件A(修改状态)
xml
<!-- ComponentA.vue -->
<script setup>
import { useUserStore } from '@/stores/user'
const user = useUserStore()
</script>
<template>
<button @click="user.login('Alice')">登录</button>
</template>
(3) 组件B(读取状态)
xml
<!-- ComponentB.vue -->
<script setup>
import { useUserStore } from '@/stores/user'
const user = useUserStore()
</script>
<template>
<div v-if="user.isLogin">欢迎, {{ user.name }}!</div>
</template>
场景2 :兄弟组件间通信
(1) 定义 Store
typescript
// stores/cart.ts
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as { id: number; name: string }[]
}),
actions: {
addItem(item: { id: number; name: string }) {
this.items.push(item)
},
removeItem(id: number) {
this.items = this.items.filter(item => item.id !== id)
}
}
})
(2)组件A(添加商品)
xml
<!-- ComponentA.vue -->
<script setup>
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
</script>
<template>
<button @click="cart.addItem({ id: 1, name: '商品A' })">加入购物车</button>
</template>
(2)组件B(显示购物车)
xml
<!-- ComponentB.vue -->
<script setup>
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
</script>
<template>
<div v-for="item in cart.items" :key="item.id">
{{ item.name }}
<button @click="cart.removeItem(item.id)">删除</button>
</div>
</template>
场景 3:复杂状态逻辑(如异步请求)
(1)定义Store
typescript
// stores/posts.ts
import { defineStore } from 'pinia'
export const usePostStore = defineStore('posts', {
state: () => ({
posts: [] as { id: number; title: string }[],
loading: false
}),
actions: {
async fetchPosts() {
this.loading = true
const res = await fetch('https://api.example.com/posts')
this.posts = await res.json()
this.loading = false
}
}
})
(2)组件调用
xml
<!-- PostList.vue -->
<script setup>
import { usePostStore } from '@/stores/posts'
const postStore = usePostStore()
// 首次加载数据
postStore.fetchPosts()
</script>
<template>
<div v-if="postStore.loading">加载中...</div>
<ul v-else>
<li v-for="post in postStore.posts" :key="post.id">
{{ post.title }}
</li>
</ul>
</template>
方式五、依赖注入(provie、inject)
使用provide与inject可以实现祖孙组件之间的传值,避免组件过深导致的props传值困难同时又没必要使用状态管理。
爷组件
xml
<template>
<div class="page" @click="count++">
<TwoPage />
</div>
</template>
<script setup lang="ts">
import TwoPage from "./twoPage.vue";
import { ref, provide } from "vue";
const count = ref(0);
provide("count", count);
</script>
父组件
xml
<template>
<div class="page">
<threePage />
</div>
</template>
<script setup lang="ts">
import threePage from "./threePage.vue";
</script>
子组件
xml
<template>
<div class="page">{{ count }}</div>
</template>
<script setup lang="ts">
import { inject } from "vue";
const count = inject("count");
</script>
方式六、mitt(),不大建议使用,建议用Pinia
全局事件总线mitt库(父子通信,兄弟通信都可,兼容vue2、vue3)
mitt
是一个 极简的事件总线库 (约 200 bytes),用于组件间的 跨层级通信,适用于:
- 非父子组件通信(兄弟组件、祖孙组件等)
- 简单场景下的全局事件监听/触发
- 替代 Vue 2 的
$emit/$on
(Event Bus 模式)
安装
npn install mitt
封装JS文件,方便调用
javascript
import mitt from 'mitt'
const $emmiter = mitt()
export default $emmiter
使用
emitter实例中有四个方法,分别为emit「触发事件」、on「绑定事件」、off「解绑事件」、all「所有绑定事件」。 接收数据方:定义事件及回调函数
父组件A.vue
xml
<template>
<div>
<B></B>
<C></C>
A接收到的信息:{{ thing }}
</div>
</template>
<script setup>
import B from './B.vue'
import C from './C.vue'
import {ref} from 'vue'
import $mitter from '../../core/eventbus'
let thing = ref('')
$mitter.on('send-data', (value)=>{
thing.value=value;
})
</script>
子组件B.vue
xml
<template>
<div id="pageB">
<button @click="handeler">B点击</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
import $mitter from '../../core/eventbus';
let data = ref('bbbb11111');
function handeler(){
$mitter.emit('send-data', data.value)
}
</script>
子组件C.vue
xml
<template>
<div id="pageC">
<span>C接收到:{{thing}}</span>
</div>
</template>
<script setup>
import {ref} from 'vue'
import $mitter from '../../core/eventbus';
let thing = ref('');
$mitter.on('send-data', (value)=>{
thing.value=value;
})
</script>
注意:接收数据方,需要在组件卸载时,及时解绑事件,避免内存泄漏。
javascript
import {onUnmounted} from 'vue'
onUnmounted(()=>{
$eminter.off('send-data') //解绑指定事件
})