Vue2 和 Vue3 组件通信方式对比

下面是 Vue2 和 Vue3 中组件通信方式的详细对比和说明:

一、共同支持的通信方式

1. Props / $emit (父子组件通信)

Vue2 和 Vue3 用法相同

// 父组件
<Child :message="msg" @update="handleUpdate" />

// 子组件
this.$emit('update', newValue)

2. 事件总线 (Event Bus)

Vue2 和 Vue3 用法相似

// Vue2
const bus = new Vue()
bus.$emit('event')
bus.$on('event', callback)

// Vue3 (需要引入第三方事件库或自定义)
import { mitt } from 'mitt'
const emitter = mitt()
emitter.emit('event')
emitter.on('event', callback)

3. Vuex 状态管理

Vue2 和 Vue3 用法基本相同

// 组件中使用
this.$store.commit('mutation')
this.$store.dispatch('action')

4. provide / inject

Vue2 和 Vue3 都支持,但 Vue3 的 Composition API 更强大

// Vue2
export default {
  provide() {
    return { theme: this.theme }
  },
  inject: ['theme']
}

// Vue3
import { provide, inject } from 'vue'
setup() {
  provide('theme', 'dark')
  const theme = inject('theme')
}

5. $refs

Vue2 和 Vue3 用法相同

<Child ref="child" />
this.$refs.child.method()

二、Vue2 特有通信方式

1. parent /children

this.$parent.method()
this.$children[0].data

注意:Vue3 已移除 $children API

2. $listeners / .sync 修饰符

// Vue2 .sync
<Child :title.sync="pageTitle" />

// Vue2 $listeners
<Child v-on="$listeners" />

注意:Vue3 中已移除 $listeners,.sync 被 v-model 参数替代

三、Vue3 新增/改进的通信方式

1. v-model 增强

// Vue3 支持多个 v-model
<Child v-model:first="first" v-model:second="second" />

// 子组件
emit('update:first', value)
emit('update:second', value)

2. Composition API 的 provide/inject

import { provide, inject } from 'vue'

// 提供者
provide('key', reactiveValue)

// 消费者
const value = inject('key', defaultValue)

3. 自定义 Hook 复用逻辑

// useCounter.js
export function useCounter() {
  const count = ref(0)
  function increment() {
    count.value++
  }
  return { count, increment }
}

// 组件中使用
const { count, increment } = useCounter()

4. Teleport 组件

<teleport to="body">
  <Modal /> <!-- 可以将组件渲染到DOM任意位置 -->
</teleport>

5. 响应式 API 共享状态

// store.js
import { reactive } from 'vue'
export const store = reactive({ count: 0 })

// 组件中使用
import { store } from './store'
store.count++

四、推荐选择

Vue2 项目:

  1. 简单通信:props/$emit
  2. 跨组件:事件总线/Vuex
  3. 深层嵌套:provide/inject

Vue3 项目:

  1. 简单通信:props/emit + v-model
  2. 跨组件:Vuex/Pinia + Composition API
  3. 状态共享:响应式API + provide/inject
  4. 逻辑复用:自定义Hook

五、注意事项

  1. Vue3 中移除了 on,off, $once 方法,事件总线需要第三方库
  2. Vue3 推荐使用 Pinia 替代 Vuex 作为状态管理工具
  3. Composition API 提供了更灵活的状态共享方式
  4. 避免过度使用 parent/children,会使组件耦合度过高

根据项目需求和组件关系选择合适的通信方式,Vue3 的组合式API为组件通信提供了更多灵活的选择。

Vue2 和 Vue3 组件通信方式超详细解析

下面我将从8个维度全面对比Vue2和Vue3的组件通信方式,包含原理、使用场景、最佳实践和完整代码示例。

一、Props / $emit 通信(父子组件)

Vue2 实现

// 父组件 Parent.vue
<template>
  <Child 
    :title="pageTitle" 
    :content="pageContent"
    @title-change="handleTitleChange"
  />
</template>

<script>
export default {
  data() {
    return {
      pageTitle: '初始标题',
      pageContent: '初始内容'
    }
  },
  methods: {
    handleTitleChange(newTitle) {
      this.pageTitle = newTitle
      console.log('标题更新为:', newTitle)
    }
  }
}
</script>

// 子组件 Child.vue
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ content }}</p>
    <button @click="updateTitle">更新标题</button>
  </div>
</template>

<script>
export default {
  props: {
    title: String,
    content: String
  },
  methods: {
    updateTitle() {
      this.$emit('title-change', '新标题-' + Date.now())
    }
  }
}
</script>

Vue3 实现

// 父组件 Parent.vue
<template>
  <Child 
    :title="pageTitle" 
    :content="pageContent"
    @title-change="handleTitleChange"
  />
</template>

<script setup>
import { ref } from 'vue'

const pageTitle = ref('初始标题')
const pageContent = ref('初始内容')

const handleTitleChange = (newTitle) => {
  pageTitle.value = newTitle
  console.log('标题更新为:', newTitle)
}
</script>

// 子组件 Child.vue
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ content }}</p>
    <button @click="updateTitle">更新标题</button>
  </div>
</template>

<script setup>
const props = defineProps({
  title: String,
  content: String
})

const emit = defineEmits(['title-change'])

const updateTitle = () => {
  emit('title-change', '新标题-' + Date.now())
}
</script>

对比分析

特性 Vue2 Vue3
定义方式 options API script setup + 组合式API
props声明 props选项 defineProps宏
事件定义 模板中直接使用$emit defineEmits宏
TypeScript 需要额外类型声明 完美支持类型推断

二、事件总线通信(跨组件)

Vue2 实现

// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()

// 组件A(发布事件)
import { EventBus } from './eventBus'
EventBus.$emit('global-event', { data: 123 })

// 组件B(订阅事件)
import { EventBus } from './eventBus'
EventBus.$on('global-event', payload => {
  console.log('收到事件:', payload)
})

// 组件卸载时需要手动移除监听
beforeDestroy() {
  EventBus.$off('global-event')
}

Vue3 实现(推荐使用mitt)

// eventBus.js
import mitt from 'mitt'
export const emitter = mitt()

// 组件A(发布事件)
import { emitter } from './eventBus'
emitter.emit('global-event', { data: 123 })

// 组件B(订阅事件)
import { emitter } from './eventBus'
import { onMounted, onUnmounted } from 'vue'

onMounted(() => {
  emitter.on('global-event', payload => {
    console.log('收到事件:', payload)
  })
})

onUnmounted(() => {
  emitter.off('global-event')
})

深度解析

  1. Vue3移除$on原因:减少体积,解耦事件系统
  2. mitt优势
    • 更小(200字节)
    • 支持通配符'*'监听所有事件
    • 清晰的TypeScript支持
  3. 性能考虑
    • 大型应用慎用,可能造成事件混乱
    • 适合小范围组件通信

三、Vuex/Pinia 状态管理

Vue2 + Vuex

// store/index.js
export default new Vuex.Store({
  state: {
    count: 0,
    user: null
  },
  mutations: {
    increment(state) {
      state.count++
    },
    setUser(state, user) {
      state.user = user
    }
  },
  actions: {
    async fetchUser({ commit }) {
      const user = await api.getUser()
      commit('setUser', user)
    }
  }
})

// 组件中使用
this.$store.commit('increment')
this.$store.dispatch('fetchUser')
const count = this.$store.state.count

Vue3 + Pinia(推荐)

// stores/counter.js
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    user: null
  }),
  actions: {
    increment() {
      this.count++
    },
    async fetchUser() {
      this.user = await api.getUser()
    }
  }
})

// 组件中使用
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.increment()
counter.fetchUser()
const count = counter.count

核心差异对比

特性 Vuex Pinia
API风格 命令式 组合式
模块系统 需要modules 自动模块化
TypeScript 需要类型定义 开箱即用
体积 较大 更轻量
调试工具 Vue Devtools Vue Devtools
异步处理 需要actions 可直接修改状态

四、provide/inject(依赖注入)

Vue2 实现

// 祖先组件
export default {
  provide() {
    return {
      themeData: {
        color: 'dark',
        toggleTheme: this.toggleTheme
      }
    }
  },
  methods: {
    toggleTheme() {
      // 切换主题逻辑
    }
  }
}

// 后代组件
export default {
  inject: ['themeData'],
  created() {
    console.log(this.themeData.color)
  }
}

Vue3 实现(组合式API)

// 祖先组件
import { provide, reactive } from 'vue'

setup() {
  const theme = reactive({
    color: 'dark',
    toggleTheme: () => {
      theme.color = theme.color === 'dark' ? 'light' : 'dark'
    }
  })
  
  provide('theme', theme)
}

// 后代组件
import { inject } from 'vue'

setup() {
  const theme = inject('theme')
  console.log(theme.color)
  
  return { theme }
}

高级用法

  1. 响应式注入
// 提供响应式对象
const state = reactive({ count: 0 })
provide('sharedState', readonly(state)) // 使用readonly防止意外修改
  1. 注入默认值
const config = inject('app-config', { theme: 'default' })
  1. Symbol作为key(避免命名冲突):
// keys.js
export const THEME_KEY = Symbol('theme')

// 提供者
provide(THEME_KEY, theme)

// 消费者
const theme = inject(THEME_KEY)

五、模板引用通信($refs)

Vue2 实现

// 父组件
<template>
  <ChildComponent ref="child" />
  <button @click="callChildMethod">调用子方法</button>
</template>

<script>
export default {
  methods: {
    callChildMethod() {
      this.$refs.child.doSomething()
      console.log(this.$refs.child.someData)
    }
  }
}
</script>

// 子组件
export default {
  data() {
    return {
      someData: '子组件数据'
    }
  },
  methods: {
    doSomething() {
      console.log('子组件方法被调用')
    }
  }
}

Vue3 实现

// 父组件
<template>
  <ChildComponent ref="childRef" />
  <button @click="callChildMethod">调用子方法</button>
</template>

<script setup>
import { ref } from 'vue'

const childRef = ref(null)

const callChildMethod = () => {
  childRef.value.doSomething()
  console.log(childRef.value.someData)
}
</script>

// 子组件
<script setup>
import { ref } from 'vue'

const someData = ref('子组件数据')

const doSomething = () => {
  console.log('子组件方法被调用')
}

// 需要暴露的属性和方法
defineExpose({
  someData,
  doSomething
})
</script>

关键区别

  1. Vue3必须使用defineExpose:明确暴露哪些内容可被访问
  2. TypeScript支持:Vue3能获得完整的类型提示
  3. 组合式API:使用ref引用替代this.$refs

六、v-model 双向绑定

Vue2 实现

// 父组件
<ChildComponent v-model="pageTitle" />

// 等价于
<ChildComponent 
  :value="pageTitle"
  @input="pageTitle = $event"
/>

// 子组件
export default {
  props: ['value'],
  methods: {
    updateValue(newVal) {
      this.$emit('input', newVal)
    }
  }
}

Vue3 实现

// 父组件
<ChildComponent v-model="pageTitle" />

// 等价于
<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="newValue => pageTitle = newValue"
/>

// 子组件
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])

const updateValue = (e) => {
  emit('update:modelValue', e.target.value)
}
</script>

Vue3 高级用法

  1. 多个v-model绑定
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

// 子组件
defineProps({
  firstName: String,
  lastName: String
})
defineEmits(['update:firstName', 'update:lastName'])
  1. 自定义修饰符
<MyComponent v-model.capitalize="myText" />

// 子组件中可以处理修饰符
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})

const emitValue = (e) => {
  let value = e.target.value
  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }
  emit('update:modelValue', value)
}

七、本地存储通信

通用实现方案

// storage.js
export const storage = {
  get(key) {
    const value = localStorage.getItem(key)
    return value ? JSON.parse(value) : null
  },
  set(key, value) {
    localStorage.setItem(key, JSON.stringify(value))
  },
  remove(key) {
    localStorage.removeItem(key)
  },
  clear() {
    localStorage.clear()
  }
}

// 组件A(设置数据)
import { storage } from './storage'
storage.set('shared-data', { key: 'value' })

// 组件B(获取数据)
const data = storage.get('shared-data')

响应式封装(Vue3)

// useStorage.js
import { ref, watchEffect } from 'vue'

export function useStorage(key, defaultValue) {
  const data = ref(JSON.parse(localStorage.getItem(key)) || defaultValue)
  
  watchEffect(() => {
    localStorage.setItem(key, JSON.stringify(data.value))
  })
  
  return data
}

// 组件中使用
const sharedData = useStorage('shared-key', { init: 'value' })
sharedData.value.newProp = 'update' // 会自动同步到localStorage

注意事项

  1. 存储限制:localStorage通常有5MB限制
  2. 性能影响:频繁操作可能影响性能
  3. 安全考虑:不要存储敏感信息
  4. 类型安全:JSON序列化会丢失函数和原型链

八、Web Workers 跨线程通信

Vue 集成方案

// worker.js
self.onmessage = function(e) {
  console.log('Worker收到:', e.data)
  const result = heavyCalculation(e.data)
  self.postMessage(result)
}

function heavyCalculation(data) {
  // 耗时计算...
  return data * 2
}

// 组件中使用
const worker = new Worker('./worker.js')

// 发送消息
worker.postMessage(42)

// 接收消息
worker.onmessage = (e) => {
  console.log('主线程收到:', e.data)
}

// 错误处理
worker.onerror = (error) => {
  console.error('Worker错误:', error)
}

Vue3 封装示例

// useWorker.js
import { ref } from 'vue'

export function useWorker(workerPath) {
  const result = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  const worker = new Worker(workerPath)
  
  const postMessage = (data) => {
    loading.value = true
    worker.postMessage(data)
  }
  
  worker.onmessage = (e) => {
    result.value = e.data
    loading.value = false
  }
  
  worker.onerror = (e) => {
    error.value = e
    loading.value = false
  }
  
  return {
    result,
    error,
    loading,
    postMessage
  }
}

// 组件中使用
const { result, postMessage } = useWorker('./worker.js')
postMessage(42)

最佳实践总结

  1. 父子通信

    • 简单数据流:props/emit
    • 双向绑定:v-model(Vue3支持多v-model)
  2. 兄弟组件

    • 小应用:事件总线
    • 中大型应用:Pinia/Vuex
  3. 深层嵌套

    • provide/inject(Vue3组合式API更强大)
  4. 复杂状态

    • Vue3首选Pinia
    • Vue2大型项目用Vuex
  5. 跨组件方法调用

    • $refs(Vue3需要defineExpose)
    • 优先考虑状态管理而非直接方法调用
  6. 持久化数据

    • 本地存储方案
    • 考虑加密敏感数据
  7. 性能敏感操作

    • Web Workers后台计算
    • 避免在main线程执行耗时操作
  8. TypeScript项目

    • Vue3 + Pinia + Composition API获得最佳类型支持
    • 为Vue2的Vuex添加类型定义

根据项目规模、团队习惯和技术栈选择合适的通信方式,保持一致性比追求新技术更重要。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容