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

Vue3 <script setup>下tiptap配合v-model异步赋值问题求解

问题场景

我在Vue.js单文件组件(SFC)的<script setup>模式下集成tiptap富文本编辑器,自定义编辑器组件代码如下:
TextEditor.vue

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

<script lang="ts" setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const props = defineProps({
  modelValue: {
    type: String,
    default: "",
  }
})

const emit = defineEmits(['update:modelValue'])

const editor = useEditor({
  content: props.modelValue,
  extensions: [StarterKit],
  onUpdate: ({editor}) => {
    let content = editor.getHTML()
    emit('update:modelValue', content)
  }
})
</script>

父组件中通过v-model绑定异步接口返回的内容:

<template>
  <text-editor v-model="myModel.content" />
</template>

问题现象

  • 若<text-editor>组件在myModel.content赋值完成之后加载,编辑器功能完全正常
  • 若组件在数据库接口返回数据、给myModel.content赋值之前就完成加载,编辑器内容会一直为空,不会随异步赋值更新

参考官方示例,我知道需要通过watch监听props.modelValue变化同步编辑器内容,参考实现如下:

watch(() => props.modelValue, (newValue, oldValue) => {
  const isSame = newValue === oldValue
  console.log(`Same: ${isSame}`)
  if (isSame) {
    return
  }
  editor.commands.setContent(newValue, false)
})

但实际运行时发现useEditor返回的editor是ShallowRef类型,直接访问commands属性会报错,无法正常调用setContent方法,需要找到<script setup>模式下正确实现异步内容同步的方案。

实现方案

核心问题是useEditor返回的是浅层响应式Ref对象,不能直接访问实例属性,必须通过.value获取真实编辑器实例,同时要做实例存在性校验、更新循环拦截,具体实现步骤:

  1. 从vue中导入watch方法
  2. 编写watch监听逻辑时,所有编辑器实例的操作都基于editor.value访问
  3. 增加两层判断避免报错和死循环:
    • 判断editor.value是否存在,跳过编辑器未初始化的阶段
    • 判断新传入的modelValue和编辑器当前内容是否一致,避免编辑器自身更新触发emit后反向触发watch,造成循环更新
  4. 调用setContent时第二个参数传false,禁止该次更新触发onUpdate事件,进一步避免循环触发

完整修改后的script部分代码:

<script lang="ts" setup>
import { watch } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'

const props = defineProps({
  modelValue: {
    type: String,
    default: "",
  }
})

const emit = defineEmits(['update:modelValue'])

const editor = useEditor({
  content: props.modelValue,
  extensions: [StarterKit],
  onUpdate: ({editor}) => {
    let content = editor.getHTML()
    emit('update:modelValue', content)
  }
})

// 新增内容同步监听
watch(
  () => props.modelValue,
  (newValue) => {
    // 编辑器未初始化时直接返回
    if (!editor.value) return
    // 内容无差异时不更新,避免循环
    if (newValue === editor.value.getHTML()) return
    // 更新编辑器内容,不触发onUpdate事件
    editor.value.commands.setContent(newValue, false)
  }
)
</script>

补充注意点:

  • 所有对tiptap编辑器实例的方法、属性调用,都需要通过editor.value访问,和普通Ref对象的使用规则一致
  • 不需要给watch加immediate: true,useEditor初始化时已经会读取一次props.modelValue作为初始内容,加immediate反而可能在实例未就绪时触发逻辑报错
  • 如果编辑器有销毁/重建逻辑,watch逻辑不需要额外调整,实例存在性判断会自动跳过无效阶段

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 05:00:45