Skip to content

🔄 Vue3组件通信完全指南

NOTE

本文详细介绍Vue3中所有组件通信方式,包含最佳实践和使用场景!

📊 通信方式总览

mindmap
  root((Vue3组件通信))
    Props/Emit
      父子组件
      单向数据流
    Provide/Inject
      跨层级通信
      依赖注入
    Event Bus
      事件总线
      全局事件
    Vuex/Pinia
      状态管理
      全局状态
    Refs
      直接访问
      组件实例
    v-model
      双向绑定
      组件绑定

🎯 Props/Emit

1. 基础使用

父组件传递数据给子组件
vue
<!-- 父组件 Parent.vue -->
<template>
  <child-component
    :message="message"
    :user-info="userInfo"
    @update="handleUpdate"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 定义数据
const message = ref('Hello from parent')
const userInfo = ref({
  name: 'John',
  age: 30
})

// 处理子组件事件
const handleUpdate = (newValue: string) => {
  console.log('Updated:', newValue)
}
</script>

<!-- 子组件 ChildComponent.vue -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ userInfo.name }}</p>
    <button @click="updateParent">Update</button>
  </div>
</template>

<script setup lang="ts">
// 定义props
const props = defineProps<{
  message: string
  userInfo: {
    name: string
    age: number
  }
}>()

// 定义emit
const emit = defineEmits<{
  (e: 'update', value: string): void
}>()

const updateParent = () => {
  emit('update', 'New Value')
}
</script>

2. Props验证

typescript
// 使用运行时声明
defineProps({
  propA: String,
  propB: {
    type: Number,
    required: true,
    default: 100,
    validator: (value: number) => value > 0
  }
})

// 使用类型声明(推荐)
defineProps<{
  propA: string
  propB: number
  propC?: boolean
}>()

// 带默认值的类型声明
withDefaults(defineProps<{
  propA: string
  propB: number
  propC?: boolean
}>(), {
  propC: false
})

🌟 Provide/Inject

1. 基础使用

vue
<!-- 父组件或根组件 -->
<script setup lang="ts">
import { provide, ref } from 'vue'

const theme = ref('dark')
const updateTheme = (newTheme: string) => {
  theme.value = newTheme
}

// 提供数据和方法
provide('theme', {
  theme,
  updateTheme
})
</script>

<!-- 子组件(任意层级) -->
<script setup lang="ts">
import { inject } from 'vue'

// 注入数据和方法
const { theme, updateTheme } = inject('theme', {
  theme: ref('light'),
  updateTheme: (theme: string) => {}
})
</script>

2. 类型安全的Provide/Inject

typescript
// 定义注入键类型
type ThemeKey = 'theme'

// 定义注入值类型
interface ThemeContext {
  theme: Ref<string>
  updateTheme: (theme: string) => void
}

// 提供者组件
provide<ThemeContext>(ThemeKey, {
  theme: ref('dark'),
  updateTheme: (newTheme) => {}
})

// 注入者组件
const theme = inject<ThemeContext>(ThemeKey)

⚡ Event Bus

1. 使用mitt实现事件总线

typescript
// eventBus.ts
import mitt from 'mitt'

type Events = {
  'user:login': { id: number; name: string }
  'user:logout': void
}

export const eventBus = mitt<Events>()

// 组件A
import { eventBus } from './eventBus'

// 发送事件
eventBus.emit('user:login', {
  id: 1,
  name: 'John'
})

// 组件B
import { eventBus } from './eventBus'

// 监听事件
eventBus.on('user:login', (user) => {
  console.log('User logged in:', user)
})

// 清理事件
onUnmounted(() => {
  eventBus.off('user:login')
})

💾 Vuex/Pinia

1. Pinia示例

typescript
// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    isLoggedIn: false
  }),
  getters: {
    userStatus: (state) => 
      state.isLoggedIn ? 'Logged In' : 'Guest'
  },
  actions: {
    login(name: string) {
      this.name = name
      this.isLoggedIn = true
    },
    logout() {
      this.name = ''
      this.isLoggedIn = false
    }
  }
})

// 组件中使用
<script setup lang="ts">
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 使用状态
console.log(userStore.name)

// 使用getter
console.log(userStore.userStatus)

// 使用action
userStore.login('John')
</script>

🎯 Refs直接访问

1. 模板引用

vue
<template>
  <child-component ref="childRef" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

// 定义ref
const childRef = ref<InstanceType<typeof ChildComponent>>()

onMounted(() => {
  // 访问子组件实例
  childRef.value?.someMethod()
})
</script>

🔄 v-model组件绑定

1. 基础使用

vue
<!-- 父组件 -->
<template>
  <custom-input
    v-model="searchText"
    v-model:title="title"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'

const searchText = ref('')
const title = ref('')
</script>

<!-- 子组件 CustomInput.vue -->
<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
  <input
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

<script setup lang="ts">
defineProps<{
  modelValue: string
  title: string
}>()

defineEmits<{
  (e: 'update:modelValue', value: string): void
  (e: 'update:title', value: string): void
}>()
</script>

📝 最佳实践建议

1. 选择通信方式的考虑因素

graph TD
    A[组件关系] --> B{是否父子组件?}
    B -->|是| C[Props/Emit]
    B -->|否| D{是否跨多层?}
    D -->|是| E[Provide/Inject]
    D -->|否| F{是否全局状态?}
    F -->|是| G[Pinia]
    F -->|否| H[Event Bus]

2. 性能优化建议

typescript
// 1. Props解构优化
// 不推荐
const { title } = props
watch(title, () => {})

// 推荐
watch(() => props.title, () => {})

// 2. 提供响应式数据
// 不推荐
provide('theme', theme.value)

// 推荐
provide('theme', readonly(theme))

// 3. 合理使用事件总线
// 及时清理事件监听
onUnmounted(() => {
  eventBus.all.clear()
})

📚 扩展阅读


🎯 补充:Vue3组合式API问答指南

1. 基础概念类问题

Q1: 什么是组合式API?为什么要使用它?

答题思路:先说概念,再说解决的问题,最后说优势

markdown
1. 概念:
   - 组合式API是Vue3引入的一种新的组件逻辑组织方式
   - 它允许我们使用函数而不是选项来组织组件逻辑

2. 解决的问题:
   - 选项式API中代码逻辑分散
   - 代码复用困难
   - 类型推导不友好

3. 主要优势:
   - 更好的逻辑复用
   - 更灵活的代码组织
   - 更好的类型推导
   - 更小的打包体积(得益于tree-shaking)

Q2: setup函数的作用是什么?

markdown
1. 核心功能:
   - 组合式API的入口点
   - 在组件创建之前执行
   - 返回的对象会暴露给模板

2. 特点:
   - 没有this上下文
   - 可以访问props和context
   - 可以是异步函数

3. 使用方式:
   - <script setup> 语法糖(推荐)
   - 手动return暴露数据

2. 响应式系统问题

Q1: ref和reactive的区别?

typescript
// 答题示例:
1. 使用场景不同:
   - ref主要用于基本类型
   const count = ref(0)
   
   - reactive用于对象类型
   const state = reactive({ count: 0 })

2. 访问方式不同:
   - ref需要.value
   console.log(count.value)
   
   - reactive直接访问
   console.log(state.count)

3. 解构行为不同:
   - ref保持响应性
   const { count } = useCount() // count是ref
   
   - reactive解构会失去响应性
   const { user } = userState // 失去响应性

Q2: 如何处理响应式数据的常见问题?

typescript
// 1. 解构响应式对象
// ❌ 错误方式
const { name, age } = reactive({ name: 'John', age: 30 })

// ✅ 正确方式
const state = reactive({ name: 'John', age: 30 })
const { name, age } = toRefs(state)

// 2. 数组响应式
// ❌ 错误方式
const list = reactive([1, 2, 3])
list[0] = 4 // 可能不会触发更新

// ✅ 正确方式
const list = ref([1, 2, 3])
list.value[0] = 4

3. 生命周期问题

Q1: 组合式API中的生命周期钩子有哪些?

typescript
// 答题示例:
1. 主要钩子函数:
   - onBeforeMount
   - onMounted
   - onBeforeUpdate
   - onUpdated
   - onBeforeUnmount
   - onUnmounted

2. 特殊钩子:
   - onErrorCaptured
   - onRenderTracked
   - onRenderTriggered

3. 使用示例:
onMounted(() => {
  // 组件挂载后执行
  fetchData()
})

onUnmounted(() => {
  // 清理工作
  clearInterval(timer)
})

4. 实践经验问题

Q1: 如何使用组合式API实现代码复用?

typescript
// 答题示例:
// 1. 封装组合式函数
function useCounter() {
  const count = ref(0)
  
  const increment = () => {
    count.value++
  }
  
  const decrement = () => {
    count.value--
  }
  
  return {
    count,
    increment,
    decrement
  }
}

// 2. 在组件中使用
export default {
  setup() {
    const { count, increment } = useCounter()
    
    return {
      count,
      increment
    }
  }
}

Q2: 组合式API中如何处理异步操作?

typescript
// 答题示例:
function useAsyncData() {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)

  const fetchData = async () => {
    loading.value = true
    try {
      data.value = await api.getData()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    loading,
    error,
    fetchData
  }
}

5. 性能优化问题

Q1: 如何优化组合式API的性能?

typescript
// 答题要点:

1. 计算属性缓存:
const cachedValue = computed(() => {
  return expensiveOperation(props.value)
})

2. 避免不必要的响应式:
// 大数据使用shallowRef
const bigData = shallowRef(largeArray)

3. 及时清理:
onUnmounted(() => {
  // 清理订阅、定时器等
})

4. 合理使用watchEffect:
// 自动收集依赖
watchEffect(() => {
  // 只有依赖变化时才执行
})

📝 答题技巧

  1. 结构化回答

    • 先说概念
    • 再说用法
    • 最后举例
  2. 突出重点

    • 核心特性
    • 最佳实践
    • 实际经验
  3. 展示深度

    • 提及原理
    • 说明优化方案
    • 分享踩坑经验
  4. 准备示例

    • 准备简单代码示例
    • 准备实际项目案例
    • 准备性能优化案例

返回顶部