马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
Vue3 组件通讯方式全解(10种方案)
一、组件通讯方式概览
通讯方式适用场景数据流向复杂度Props/自界说事件父子组件简朴通讯父 ↔ 子⭐v-model 双向绑定父子表单组件父 ↔ 子⭐⭐Provide/Inject跨层级组件通讯祖先 → 子女⭐⭐事件总线恣意组件间通讯恣意方向⭐⭐⭐模板引用(ref)父操作子组件父 → 子⭐Pinia 状态管理复杂应用状态共享全局共享⭐⭐⭐欣赏器存储持久化数据共享全局共享⭐⭐⭐attrs/attrs/listeners透传属性/事件父 → 深层子组件⭐⭐作用域插槽子向父通报渲染内容子 → 父⭐⭐路由参数页面间数据通报页面间⭐ 二、焦点通讯方案详解
1. Props / 自界说事件(父子通讯)
- <!-- 父组件 Parent.vue -->
- <template>
- <Child
- :message="parentMsg"
- @update="handleUpdate"
- />
- </template>
- <script setup>
- import { ref } from 'vue'
- const parentMsg = ref('Hello from parent')
- const handleUpdate = (newMsg) => {
- parentMsg.value = newMsg
- }
- </script>
- <!-- 子组件 Child.vue -->
- <script setup>
- defineProps(['message'])
- const emit = defineEmits(['update'])
- const sendToParent = () => {
- emit('update', 'New message from child')
- }
- </script>
复制代码 2. v-model 双向绑定(表单场景)
- <!-- 父组件 -->
- <CustomInput v-model="username" />
- <!-- 子组件 CustomInput.vue -->
- <template>
- <input
- :value="modelValue"
- @input="$emit('update:modelValue', $event.target.value)"
- >
- </template>
- <script setup>
- defineProps(['modelValue'])
- defineEmits(['update:modelValue'])
- </script>
复制代码 3. Provide/Inject(跨层级)
- // 祖先组件
- import { provide, ref } from 'vue'
- const theme = ref('dark')
- provide('theme', {
- theme,
- toggleTheme: () => {
- theme.value = theme.value === 'dark' ? 'light' : 'dark'
- }
- })
- // 后代组件
- import { inject } from 'vue'
- const { theme, toggleTheme } = inject('theme')
复制代码 4. 事件总线 mitt(恣意组件)
- // eventBus.js
- import mitt from 'mitt'
- export const emitter = mitt()
- // 组件A(发送)
- emitter.emit('global-event', { data: 123 })
- // 组件B(接收)
- emitter.on('global-event', (data) => {
- console.log(data)
- })
复制代码 5. Pinia 状态管理(复杂应用)
- // stores/user.js
- export const useUserStore = defineStore('user', {
- state: () => ({ name: 'Alice' }),
- actions: {
- updateName(newName) {
- this.name = newName
- }
- }
- })
- // 组件使用
- const userStore = useUserStore()
- userStore.updateName('Bob')
复制代码 6. 作用域插槽(子传父)
- <!-- 子组件 -->
- <template>
- <slot :data="childData" />
- </template>
- <script setup>
- const childData = { message: 'From child' }
- </script>
- <!-- 父组件 -->
- <Child>
- <template #default="{ data }">
- {{ data.message }}
- </template>
- </Child>
复制代码 三、进阶通讯本领
1. 多层属性透传
- // 父组件
- <GrandParent>
- <Parent>
- <Child />
- </Parent>
- </GrandParent>
- // GrandParent.vue
- <template>
- <Parent v-bind="$attrs">
- <slot />
- </Parent>
- </template>
- // 使用 inheritAttrs: false 关闭自动继承
复制代码 2. 动态组件通讯
- <component
- :is="currentComponent"
- @custom-event="handleEvent"
- />
复制代码 3. 自界说 Hook 封装
- // hooks/useCounter.js
- export function useCounter(initial = 0) {
- const count = ref(initial)
-
- const increment = () => count.value++
-
- return { count, increment }
- }
- // 组件A
- const { count: countA } = useCounter()
- // 组件B
- const { count: countB } = useCounter(10)
复制代码 4. 全局事件总线加强版
- // eventBus.ts
- type EventMap = {
- 'user-login': { userId: string }
- 'cart-update': CartItem[]
- }
- export const emitter = mitt<EventMap>()
- // 安全使用
- emitter.emit('user-login', { userId: '123' })
复制代码 四、最佳实践指南
- 简朴场景优先方案
- 父子通讯:Props + 自界说事件
- 表单组件:v-model
- 简朴共享数据:Provide/Inject
- 复杂应用推荐方案
- 全局状态管理:Pinia
- 跨组件通讯:事件总线
- 持久化数据:localStorage + Pinia 插件
- 性能优化本领
- 避免在 Props 中通报大型对象
- 使用 computed 属性优化渲染
- 对频繁触发的事件进行防抖处理
- 及时清理事件监听器
- TypeScript 加强
- // Props 类型定义
- defineProps<{
- title: string
- data: number[]
- }>()
- // 事件类型定义
- defineEmits<{
- (e: 'update', value: string): void
- (e: 'delete', id: number): void
- }>()
复制代码 五、常见题目解决方案
Q1: 如何避免 Props 层层通报?
✅ 使用 Provide/Inject 或 Pinia
Q2: 子组件如何修改父组件数据?
✅ 通过自界说事件关照父组件修改
Q3: 如何实现兄弟组件通讯?
✅ 方案1:通过共同的父组件中转
✅ 方案2:使用事件总线或 Pinia
Q4: 如何包管响应式数据安全?
✅ 使用 readonly 限定修改权限:
- provide('config', readonly(config))
复制代码 Q5: 如何实现跨路由组件通讯?
✅ 使用 Pinia 状态管理
✅ 通过路由参数通报
✅ 使用 localStorage 持久化存储
插槽详解
一、插槽焦点概念
1. 插槽作用
答应父组件向子组件插入自界说内容,实现组件的高度可定制化
2. 组件通讯对比
通讯方式数据流向内容类型Props父 → 子纯数据插槽父 → 子模板/组件/HTML片断 二、基础插槽类型
1. 默认插槽
子组件:
- <!-- ChildComponent.vue -->
- <template>
- <div class="card">
- <slot>默认内容(父组件未提供时显示)</slot>
- </div>
- </template>
复制代码 父组件:
- <ChildComponent>
- <p>自定义卡片内容</p>
- </ChildComponent>
复制代码 2. 具名插槽
子组件:
- <template>
- <div class="layout">
- <header>
- <slot name="header"></slot>
- </header>
- <main>
- <slot></slot> <!-- 默认插槽 -->
- </main>
- <footer>
- <slot name="footer"></slot>
- </footer>
- </div>
- </template>
复制代码 父组件:
- <ChildComponent>
- <template #header>
- <h1>页面标题</h1>
- </template>
- <p>主内容区域</p>
- <template v-slot:footer>
- <p>版权信息 © 2023</p>
- </template>
- </ChildComponent>
复制代码 三、作用域插槽(焦点进阶)
1. 数据通报原理
子组件向插槽通报数据 → 父组件吸收使用
子组件:
- <template>
- <ul>
- <li v-for="item in items" :key="item.id">
- <slot :item="item" :index="index"></slot>
- </li>
- </ul>
- </template>
- <script setup>
- const items = ref([{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }])
- </script>
复制代码 父组件:
- <ChildComponent>
- <template #default="{ item, index }">
- <span>{{ index + 1 }}. {{ item.name }}</span>
- <button @click="deleteItem(item.id)">删除</button>
- </template>
- </ChildComponent>
复制代码 2. 应用场景示例
场景:表格组件支持自界说列渲染
子组件:
- <template>
- <table>
- <thead>
- <tr>
- <th v-for="col in columns" :key="col.key">
- {{ col.title }}
- </th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="row in data" :key="row.id">
- <td v-for="col in columns" :key="col.key">
- <slot :name="col.key" :row="row">
- {{ row[col.key] }} <!-- 默认显示 -->
- </slot>
- </td>
- </tr>
- </tbody>
- </table>
- </template>
复制代码 父组件:
- <DataTable :data="users" :columns="columns">
- <template #action="{ row }">
- <button @click="editUser(row.id)">编辑</button>
- <button @click="deleteUser(row.id)">删除</button>
- </template>
- <template #status="{ row }">
- <span :class="row.status">{{ row.status | statusText }}</span>
- </template>
- </DataTable>
复制代码 四、动态插槽名
子组件:
- <template>
- <div class="dynamic-slot">
- <slot :name="slotName"></slot>
- </div>
- </template>
- <script setup>
- defineProps({
- slotName: {
- type: String,
- default: 'default'
- }
- })
- </script>
复制代码 父组件:
- <ChildComponent :slotName="currentSlot">
- <template #[currentSlot]>
- <p>动态插槽内容(当前使用 {{ currentSlot }} 插槽)</p>
- </template>
- </ChildComponent>
复制代码 五、高级本领
1. 插槽继续($slots)
访问子组件插槽内容:
- // 子组件内部
- const slots = useSlots()
- console.log(slots.header()) // 获取具名插槽内容
复制代码 2. 渲染函数中使用插槽
- // 使用 h() 函数创建元素
- export default {
- render() {
- return h('div', [
- this.$slots.default?.() || '默认内容',
- h('div', { class: 'footer' }, this.$slots.footer?.())
- ])
- }
- }
复制代码 六、最佳实践指南
- 定名规范
- 使用小写字母 + 连字符定名具名插槽(如 #user-avatar)
- 避免使用保存字作为插槽名(如 default、item)
- 性能优化
- <!-- 通过 v-if 控制插槽内容渲染 -->
- <template #header v-if="showHeader">
- <HeavyComponent />
- </template>
复制代码 - 类型安全(TypeScript)
- // 定义作用域插槽类型
- defineSlots<{
- default?: (props: { item: T; index: number }) => any
- header?: () => any
- footer?: () => any
- }>()
复制代码 七、常见题目解答
Q1:如何强制要求必须提供某个插槽
- // 子组件中验证
- export default {
- mounted() {
- if (!this.$slots.header) {
- console.error('必须提供 header 插槽内容')
- }
- }
- }
复制代码 Q2:插槽内容如何访问子组件方法?
- <!-- 子组件暴露方法 -->
- <slot :doSomething="handleAction"></slot>
- <!-- 父组件使用 -->
- <template #default="{ doSomething }">
- <button @click="doSomething">触发子组件方法</button>
- </template>
复制代码 Q3:如何实现插槽内容过渡动画?
- <transition name="fade" mode="out-in">
- <slot></slot>
- </transition>
复制代码 八、综合应用案例
可设置的模态框组件
- <!-- Modal.vue -->
- <template>
- <div class="modal" v-show="visible">
- <div class="modal-header">
- <slot name="header">
- <h2>{{ title }}</h2>
- <button @click="close">×</button>
- </slot>
- </div>
-
- <div class="modal-body">
- <slot :close="close"></slot>
- </div>
- <div class="modal-footer">
- <slot name="footer">
- <button @click="close">关闭</button>
- </slot>
- </div>
- </div>
- </template>
复制代码 父组件使用:
- <Modal v-model:visible="showModal" title="自定义标题">
- <template #header>
- <h1 style="color: red;">紧急通知</h1>
- </template>
- <template #default="{ close }">
- <p>确认删除此项?</p>
- <button @click="confirmDelete; close()">确认</button>
- </template>
- <template #footer>
- <button @click="showModal = false">取消</button>
- </template>
- </Modal>
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |