如何处理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
相关产品推荐
相关产品推荐

