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

Vue3组合式API中Tiptap编辑器循环渲染失效问题求助

解决Tiptap编辑器循环渲染仅显示一个的问题

你遇到的核心问题是所有<editor-content>组件复用了同一个Editor实例。Tiptap的Editor实例会和单个DOM节点绑定,复用实例只会让最后一个(或第一个)组件渲染出编辑器,其他组件无法正常显示。必须给每个教育经历项创建独立的Editor实例,下面提供两种可行方案:

方案1:封装独立编辑器子组件(推荐)

这种方式符合Vue组件化思想,便于维护和复用:

1.1 创建EduEditor.vue子组件

<template>
  <editor-content class="editor" :editor="editor" />
</template>

<script setup>
import { ref, watch } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline'
import TextAlign from '@tiptap/extension-text-align'
import Document from '@tiptap/extension-document'

// 接收外部传入的内容和更新事件
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  }
})
const emit = defineEmits(['update:modelValue'])

// 创建当前组件专属的Editor实例
const editor = useEditor({
  content: props.modelValue,
  extensions: [
    StarterKit,
    Underline,
    Document,
    TextAlign.configure({
      types: ['heading', 'paragraph'],
    })
  ],
  // 编辑器内容更新时同步到父组件
  onUpdate: ({ editor }) => {
    emit('update:modelValue', editor.getHTML())
  }
})

// 监听父组件内容变化,同步到编辑器
watch(() => props.modelValue, (newVal) => {
  if (newVal !== editor.value?.getHTML()) {
    editor.value?.commands.setContent(newVal)
  }
})
</script>

1.2 父组件中使用子组件

修改循环渲染部分,替换为子组件,并使用唯一标识作为key(避免用index,防止列表渲染异常):

<!-- 父组件模板 -->
<div v-for="item in education.fields" :key="item.id">
  <edu-editor v-model="item.desc" />
</div>

1.3 修改添加字段函数(生成唯一ID)

const addingEduField = () => {
  education.value.fields.push({
    id: Date.now().toString(), // 生成唯一标识
    edu: "",
    school: "",
    city: "",
    startDate: { month: "", year: "" },
    endDate: { month: "", year: "", present: false },
    desc: '',
    key: true,
  });
};

方案2:父组件内动态创建编辑器实例

如果不想封装子组件,可以直接在父组件中为每个字段创建独立实例:

2.1 修改父组件逻辑

import { ref, onBeforeUnmount } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
// 其他导入保持不变

const education = ref({
  inputText: "",
  menu: false,
  self: false,
  fields: [],
});

// 为单个字段创建编辑器实例
const createEditor = (field) => {
  const editor = useEditor({
    content: field.desc,
    extensions: [
      StarterKit,
      Underline,
      Document,
      TextAlign.configure({
        types: ['heading', 'paragraph'],
      })
    ],
    onUpdate: ({ editor }) => {
      field.desc = editor.getHTML()
    }
  })
  field.editorInstance = editor
  return editor
}

// 添加字段时同时创建编辑器
const addingEduField = () => {
  const newField = {
    id: Date.now().toString(),
    edu: "",
    school: "",
    city: "",
    startDate: { month: "", year: "" },
    endDate: { month: "", year: "", present: false },
    desc: '',
    key: true,
  }
  createEditor(newField)
  education.value.fields.push(newField)
}

// 组件销毁前销毁所有编辑器,避免内存泄漏
onBeforeUnmount(() => {
  education.value.fields.forEach(field => {
    field.editorInstance?.value?.destroy()
  })
})

2.2 父组件模板修改

<div v-for="item in education.fields" :key="item.id">
  <editor-content class="editor" :editor="item.editorInstance" />
</div>

核心原理说明

Tiptap的Editor实例与DOM节点是一一绑定的,当多个EditorContent组件使用同一个实例时,实例只会关联最后完成挂载的DOM节点,导致其他组件无法渲染编辑器。给每个教育项分配独立实例,才能保证每个编辑器正常工作。

内容的提问来源于stack exchange,提问作者abd elmalik abd elghafar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 20:35:05