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获取真实编辑器实例,同时要做实例存在性校验、更新循环拦截,具体实现步骤:
- 从vue中导入
watch方法 - 编写watch监听逻辑时,所有编辑器实例的操作都基于
editor.value访问 - 增加两层判断避免报错和死循环:
- 判断
editor.value是否存在,跳过编辑器未初始化的阶段 - 判断新传入的
modelValue和编辑器当前内容是否一致,避免编辑器自身更新触发emit后反向触发watch,造成循环更新
- 判断
- 调用
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
相关产品推荐
相关产品推荐

