You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3中如何通过编程方式订阅子组件事件?

Vue3中监听子组件事件的替代方案(替代Vue2的$on)

针对你提到的「持有子组件引用/VNode时批量订阅事件、递归场景下无法用模板v-on」的需求,以下是两种可行的解决方案:

方案一:修改插槽VNode的props绑定事件(最通用,无需子组件配合)

Vue3中,所有事件监听都会被编译为VNode props中以on开头的属性(比如onClick对应click事件)。我们可以递归遍历插槽返回的VNodes,直接修改它们的props,将自定义事件处理函数与原有事件合并,从而实现批量监听。

代码示例

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

// 递归处理VNode,合并事件监听函数
const enhanceVNodeEvents = (vnode, eventHandlers) => {
  if (!vnode) return

  // 遍历要监听的事件,合并到VNode的props中
  Object.entries(eventHandlers).forEach(([eventName, handler]) => {
    // 转换为Vue事件的props格式:click → onClick
    const propKey = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`
    
    if (vnode.props?.[propKey]) {
      // 若原有事件已存在,将自定义处理函数追加进去
      const originalHandler = vnode.props[propKey]
      vnode.props[propKey] = Array.isArray(originalHandler)
        ? [...originalHandler, handler]
        : [originalHandler, handler]
    } else {
      // 若原有事件不存在,直接添加
      vnode.props = { ...vnode.props, [propKey]: handler }
    }
  })

  // 递归处理子VNodes(支持组件嵌套、原生元素子节点的场景)
  if (vnode.children) {
    if (Array.isArray(vnode.children)) {
      vnode.children.forEach(child => enhanceVNodeEvents(child, eventHandlers))
    } else if (typeof vnode.children === 'object') {
      enhanceVNodeEvents(vnode.children, eventHandlers)
    }
  }
}

const slots = useSlots()
const defaultSlot = slots.default?.()

// 定义需要监听的事件及处理逻辑
const eventHandlers = {
  click: (e) => console.log('监听子节点click事件:', e),
  input: (value) => console.log('监听子节点input事件:', value),
  // 可添加任意需要监听的事件类型
}

// 批量处理所有插槽内的VNode
defaultSlot?.forEach(vnode => enhanceVNodeEvents(vnode, eventHandlers))
</script>

<template>
  <div class="wrapper">
    <slot />
  </div>
</template>

方案说明

  • 无需修改子组件代码,兼容原生HTML元素和所有Vue组件;
  • 支持递归处理嵌套节点,满足递归场景需求;
  • 事件处理函数会与原有事件共存,不会覆盖子组件原本的事件逻辑。

方案二:子组件暴露订阅方法(需子组件配合)

如果子组件是你可控的,可以让子组件暴露自定义的事件订阅/取消方法,模拟Vue2的$on/$off行为。

子组件代码示例

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

// 存储事件监听函数
const eventListeners = ref(new Map())

// 暴露订阅、取消订阅方法
defineExpose({
  on: (eventName, handler) => {
    if (!eventListeners.value.has(eventName)) {
      eventListeners.value.set(eventName, [])
    }
    eventListeners.value.get(eventName).push(handler)
  },
  off: (eventName, handler) => {
    const handlers = eventListeners.value.get(eventName)
    if (handlers) {
      const index = handlers.indexOf(handler)
      index > -1 && handlers.splice(index, 1)
    }
  }
})

// 自定义emit方法,同时触发原生emit和订阅的事件
const customEmit = (eventName, ...args) => {
  // 触发订阅的事件
  const handlers = eventListeners.value.get(eventName)
  handlers?.forEach(handler => handler(...args))
  // 触发原生组件事件
  defineEmits(['click', 'input'])(eventName, ...args)
}
</script>

父组件调用示例

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const childRef = ref(null)

const handleClick = (e) => console.log('通过实例订阅的click事件:', e)

onMounted(() => {
  childRef.value?.on('click', handleClick)
})

onUnmounted(() => {
  // 组件卸载时取消订阅,避免内存泄漏
  childRef.value?.off('click', handleClick)
})
</script>

<template>
  <ChildComponent ref="childRef" />
</template>

方案说明

  • 需要子组件配合实现,不适用于第三方组件;
  • 可以精确控制事件的订阅与取消,适合需要动态管理监听的场景。

内容的提问来源于stack exchange,提问作者SanBen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:03:24