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

如何处理Vue.js嵌套组件的Emitted事件传递问题?

嵌套组件事件传递方案(非全局状态管理)

我有一个ToolbarItem组件,作为Toolbar组件的子组件,而Toolbar又被用在EditorComponent中,组件层级是ToolbarItem → Toolbar → EditorComponent。每个ToolbarItem对应不同的编辑器功能函数,请问除了全局状态管理之外,还有哪些方式可以实现这类嵌套组件的事件传递?

现有组件代码

ToolbarItem组件

<template>
  <div
    class="flex cursor-pointer items-center justify-center rounded-full border-2 border-gray-300 p-1 shadow-sm transition-all duration-300 hover:scale-110 hover:bg-black hover:text-white"
    @click="$emit('event')"
    :class="isActive ? 'bg-black text-white' : ''"
  >
    <Icon @click="setActive()" :icon="icon" />
  </div>
</template>

<script setup>
import { Icon } from '@iconify/vue'
import { defineProps, defineEmits } from 'vue'
import { ref } from 'vue'
const props = defineProps({
  icon: String
})
const emits = defineEmits({
  event: String
})
const isActive = ref(false)
function setActive() {
  isActive.value = !isActive.value
  console.log(isActive.value)
}
</script>

<style scoped></style>

Toolbar组件

<template>
  <div class="flex gap-2 rounded-3xl border-2 border-gray-300 p-2 shadow-md">
    <TipTapToolbarItem icon="ooui:bold-b"></TipTapToolbarItem>
    <TipTapToolbarItem icon="clarity:italic-line"></TipTapToolbarItem>
    <TipTapToolbarItem icon="fa-solid:strikethrough"></TipTapToolbarItem>
    <TipTapToolbarItem icon="mingcute:heading-1-fill"></TipTapToolbarItem>
    <TipTapToolbarItem icon="mingcute:code-fill"></TipTapToolbarItem>
    <TipTapToolbarItem icon="tabler:blockquote"></TipTapToolbarItem>
    <TipTapToolbarItem icon="octicon:horizontal-rule-16"></TipTapToolbarItem>
  </div>
</template>

<script setup>
import TipTapToolbarItem from './TipTapToolbarItem.vue'
</script>

<style scoped></style>

EditorComponent中使用Toolbar

<template>
  <TipTapToolbar></TipTapToolbar>
  <editor-content class="h-[200px] w-[200px] bg-blue-500" placeholder="tiptap" :editor="editor" />
</template>

解决方案

1. 事件逐层传递($emit/defineEmits向上冒泡)

通过子组件触发事件,父组件监听后继续向上传递,最终由顶层的EditorComponent处理具体逻辑,是最直观的嵌套组件通信方式。

修改后代码:

ToolbarItem.vue
<template>
  <div
    class="flex cursor-pointer items-center justify-center rounded-full border-2 border-gray-300 p-1 shadow-sm transition-all duration-300 hover:scale-110 hover:bg-black hover:text-white"
    @click="handleClick"
    :class="isActive ? 'bg-black text-white' : ''"
  >
    <Icon :icon="icon" />
  </div>
</template>

<script setup>
import { Icon } from '@iconify/vue'
import { defineProps, defineEmits, ref } from 'vue'

const props = defineProps({
  icon: String,
  action: String // 新增:标识当前按钮对应的功能类型
})

const emits = defineEmits(['action-triggered'])
const isActive = ref(false)

function handleClick() {
  isActive.value = !isActive.value
  emits('action-triggered', props.action) // 触发事件并携带功能类型
}
</script>
Toolbar.vue
<template>
  <div class="flex gap-2 rounded-3xl border-2 border-gray-300 p-2 shadow-md">
    <TipTapToolbarItem 
      icon="ooui:bold-b" 
      action="bold"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
    <TipTapToolbarItem 
      icon="clarity:italic-line" 
      action="italic"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
    <TipTapToolbarItem 
      icon="fa-solid:strikethrough" 
      action="strike"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
    <!-- 其余按钮同理添加action属性和事件监听 -->
    <TipTapToolbarItem 
      icon="mingcute:heading-1-fill" 
      action="heading"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
    <TipTapToolbarItem 
      icon="mingcute:code-fill" 
      action="code"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
    <TipTapToolbarItem 
      icon="tabler:blockquote" 
      action="blockquote"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
    <TipTapToolbarItem 
      icon="octicon:horizontal-rule-16" 
      action="horizontalRule"
      @action-triggered="$emit('action-triggered', $event)"
    ></TipTapToolbarItem>
  </div>
</template>

<script setup>
import TipTapToolbarItem from './TipTapToolbarItem.vue'
import { defineEmits } from 'vue'

const emits = defineEmits(['action-triggered']) // 定义向上传递的事件
</script>
EditorComponent.vue
<template>
  <TipTapToolbar @action-triggered="handleToolbarAction"></TipTapToolbar>
  <editor-content class="h-[200px] w-[200px] bg-blue-500" placeholder="tiptap" :editor="editor" />
</template>

<script setup>
import TipTapToolbar from './TipTapToolbar.vue'
import { ref } from 'vue'

const editor = ref(null) // 假设已初始化Tiptap编辑器实例

function handleToolbarAction(action) {
  // 根据功能类型执行对应编辑器操作
  switch(action) {
    case 'bold':
      editor.value?.chain().focus().toggleBold().run()
      break
    case 'italic':
      editor.value?.chain().focus().toggleItalic().run()
      break
    case 'strike':
      editor.value?.chain().focus().toggleStrike().run()
      break
    case 'heading':
      editor.value?.chain().focus().toggleHeading({ level: 1 }).run()
      break
    case 'code':
      editor.value?.chain().focus().toggleCode().run()
      break
    case 'blockquote':
      editor.value?.chain().focus().toggleBlockquote().run()
      break
    case 'horizontalRule':
      editor.value?.chain().focus().setHorizontalRule().run()
      break
  }
}
</script>

2. 使用Vue的provide/inject API

通过顶层组件provide编辑器实例或封装好的操作方法,深层组件inject直接调用,跳过中间层级的传递,适合多层嵌套的场景。

修改后代码:

EditorComponent.vue
<template>
  <TipTapToolbar></TipTapToolbar>
  <editor-content class="h-[200px] w-[200px] bg-blue-500" placeholder="tiptap" :editor="editor" />
</template>

<script setup>
import TipTapToolbar from './TipTapToolbar.vue'
import { provide, ref } from 'vue'

const editor = ref(null)

// 提供封装好的编辑器操作方法(更安全,避免直接暴露实例)
provide('editorActions', {
  toggleBold: () => editor.value?.chain().focus().toggleBold().run(),
  toggleItalic: () => editor.value?.chain().focus().toggleItalic().run(),
  toggleStrike: () => editor.value?.chain().focus().toggleStrike().run(),
  toggleHeading: () => editor.value?.chain().focus().toggleHeading({ level:1 }).run(),
  toggleCode: () => editor.value?.chain().focus().toggleCode().run(),
  toggleBlockquote: () => editor.value?.chain().focus().toggleBlockquote().run(),
  setHorizontalRule: () => editor.value?.chain().focus().setHorizontalRule().run()
})
</script>
Toolbar.vue
<template>
  <div class="flex gap-2 rounded-3xl border-2 border-gray-300 p-2 shadow-md">
    <TipTapToolbarItem icon="ooui:bold-b" action="toggleBold"></TipTapToolbarItem>
    <TipTapToolbarItem icon="clarity:italic-line" action="toggleItalic"></TipTapToolbarItem>
    <TipTapToolbarItem icon="fa-solid:strikethrough" action="toggleStrike"></TipTapToolbarItem>
    <!-- 其余按钮同理添加action属性 -->
    <TipTapToolbarItem icon="mingcute:heading-1-fill" action="toggleHeading"></TipTapToolbarItem>
    <TipTapToolbarItem icon="mingcute:code-fill" action="toggleCode"></TipTapToolbarItem>
    <TipTapToolbarItem icon="tabler:blockquote" action="toggleBlockquote"></TipTapToolbarItem>
    <TipTapToolbarItem icon="octicon:horizontal-rule-16" action="setHorizontalRule"></TipTapToolbarItem>
  </div>
</template>

<script setup>
import TipTapToolbarItem from './TipTapToolbarItem.vue'
</script>
ToolbarItem.vue
<template>
  <div
    class="flex cursor-pointer items-center justify-center rounded-full border-2 border-gray-300 p-1 shadow-sm transition-all duration-300 hover:scale-110 hover:bg-black hover:text-white"
    @click="handleAction"
    :class="isActive ? 'bg-black text-white' : ''"
  >
    <Icon :icon="icon" />
  </div>
</template>

<script setup>
import { Icon } from '@iconify/vue'
import { defineProps, inject, ref } from 'vue'

const props = defineProps({
  icon: String,
  action: String
})

const isActive = ref(false)
const editorActions = inject('editorActions') // 注入操作方法

function handleAction() {
  isActive.value = !isActive.value
  editorActions?.[props.action]?.() // 调用对应功能方法
}
</script>

3. Props传递回调函数

在顶层EditorComponent定义每个功能的回调函数,通过Props逐层传递给ToolbarItem,点击时直接执行传入的回调,适合功能较少、层级简单的场景。

修改后代码:

EditorComponent.vue
<template>
  <TipTapToolbar 
    :onBold="handleBold"
    :onItalic="handleItalic"
    :onStrike="handleStrike"
    :onHeading="handleHeading"
    :onCode="handleCode"
    :onBlockquote="handleBlockquote"
    :onHorizontalRule="handleHorizontalRule"
  ></TipTapToolbar>
  <editor-content class="h-[200px] w-[200px] bg-blue-500" placeholder="tiptap" :editor="editor" />
</template>

<script setup>
import TipTapToolbar from './TipTapToolbar.vue'
import { ref } from 'vue'

const editor = ref(null)

function handleBold() {
  editor.value?.chain().focus().toggleBold().run()
}
function handleItalic() {
  editor.value?.chain().focus().toggleItalic().run()
}
function handleStrike() {
  editor.value?.chain().focus().toggleStrike().run()
}
function handleHeading() {
  editor.value?.chain().focus().toggleHeading({ level:1 }).run()
}
function handleCode() {
  editor.value?.chain().focus().toggleCode().run()
}
function handleBlockquote() {
  editor.value?.chain().focus().toggleBlockquote().run()
}
function handleHorizontalRule() {
  editor.value?.chain().focus().setHorizontalRule().run()
}
</script>
Toolbar.vue
<template>
  <div class="flex gap-2 rounded-3xl border-2 border-gray-300 p-2 shadow-md">
    <TipTapToolbarItem icon="ooui:bold-b" :onClick="onBold"></TipTapToolbarItem>
    <TipTapToolbarItem icon="clarity:italic-line" :onClick="onItalic"></TipTapToolbarItem>
    <TipTapToolbarItem icon="fa-solid:strikethrough" :onClick="onStrike"></TipTapToolbarItem>
    <!-- 其余按钮同理传递对应回调 -->
    <TipTapToolbarItem icon="mingcute:heading-1-fill" :onClick="onHeading"></TipTapToolbarItem>
    <TipTapToolbarItem icon="mingcute:code-fill" :onClick="onCode"></TipTapToolbarItem>
    <TipTapToolbarItem icon="tabler:blockquote" :onClick="onBlockquote"></TipTapToolbarItem>
    <TipTapToolbarItem icon="octicon:horizontal-rule-16" :onClick="onHorizontalRule"></TipTapToolbarItem>
  </div>
</template>

<script setup>
import TipTapToolbarItem from './TipTapToolbarItem.vue'
import { defineProps } from 'vue'

const props = defineProps({
  onBold: Function,
  onItalic: Function,
  onStrike: Function,
  onHeading: Function,
  onCode: Function,
  onBlockquote: Function,
  onHorizontalRule: Function
})
</script>
ToolbarItem.vue
<template>
  <div
    class="flex cursor-pointer items-center justify-center rounded-full border-2 border-gray-300 p-1 shadow-sm transition-all duration-300 hover:scale-110 hover:bg-black hover:text-white"
    @click="handleClick"
    :class="isActive ? 'bg-black text-white' : ''"
  >
    <Icon :icon="icon" />
  </div>
</template>

<script setup>
import { Icon } from '@iconify/vue'
import { defineProps, ref } from 'vue'

const props = defineProps({
  icon: String,
  onClick: Function
})

const isActive = ref(false)

function handleClick() {
  isActive.value = !isActive.value
  props.onClick?.() // 执行传入的回调函数
}
</script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 00:40:58