{T}

Vditor 配置面板联动、动态表单扩展与响应式示例页

概述

前两篇完成了 Vditor 选型与基础组件封装。本文做一个非常实用的"编辑器演示页":上方一组响应式配置表单,中间是编辑器实例,当表单值变化时实时修改编辑器配置。它的核心价值不止是演示,更是把前面封装的 Schema 动态表单在真实业务里落地——表单配置驱动组件 props,props 再驱动第三方实例行为。本文覆盖动态表单对 radio-group/checkbox-group 的扩展、组组件用动态组件渲染子节点、响应式布局能力(rowClass/columnProps),以及配置变化时深度监听的坑与 useDebounceFn 稳定重建。

学习目标

  • 理解配置面板本质是"响应式表单驱动第三方实例 options 变化"的示例页模式。
  • 能为动态表单扩展 radio-group/checkbox-group,并理解其与单组件渲染的结构差异。
  • 掌握用 <component :is> 动态渲染组组件的外层与内层子节点。
  • 知道调参面板场景下 v-model 直绑 editorOptions 比额外套 useForm 更直接。
  • 理解响应式布局必须 ElRow + 列宽 配合,以及 columnProps 断点能力的意义。
  • 看清深度监听下新旧对象是同一引用,从而用 useDebounceFn 控制重建节奏而非比较对象。

一、配置面板本质:响应式表单驱动第三方实例

这个演示页的核心思想很适合低代码和组件调试:上面是响应式配置表单,中间是编辑器实例,表单值变化即实时修改编辑器配置。它本质上是"表单配置驱动组件 props,组件 props 再驱动第三方实例行为"。

Vue SFC
<VForm v-model="editorOptions" :schema="schema" />
<BaseEditor v-model="content" :options="editorOptions" />

这种结构不仅是个 Vditor 示例页,也是对前面动态表单组件在真实业务中的一次落地演示。但它也带来一个后续必须面对的问题:一旦用上响应式配置,频繁变更会带来性能和实例重建问题。

二、动态表单扩展 radio-group / checkbox-group

原先的动态表单只处理了 radiocheckbox,没有处理 radio-groupradio-buttoncheckbox-group。这些组件和普通单选框/复选框的最大区别在于:外层是一个组组件,内层还要再渲染子项。所以不能再像普通输入框一样直接渲染单个组件,而要引入"子项描述结构"和"动态子组件渲染"。

ts
{
  label: "模式",
  prop: "mode",
  type: "radio-group",
  children: [
    { type: "radio-button", label: "所见即所得", value: "wysiwyg" },
    { type: "radio-button", label: "即时渲染", value: "ir" },
    { type: "radio-button", label: "分屏预览", value: "sv" },
  ],
}

分组型组件的关键不是外层 group,而是内层子项也要可配置。radio-groupcheckbox-group 的渲染逻辑非常相似,适合统一抽象,一旦加进动态表单,后续很多配置面板都能直接复用。

三、组组件用动态组件渲染子节点

支持 radio-group/checkbox-group 时,不能再沿用"单组件直接绑定全部属性"的老写法。根本原因在内部结构不同:有些组件直接把 label 当属性传,有些要把文本写到插槽里,组组件下面还要再嵌套子组件。最自然的方案是外层渲染组组件、内层对子项继续使用 <component :is="...">

Vue SFC
<el-radio-group v-model="model[item.prop]" v-bind="item.attrs">
  <component
    v-for="child in item.children"
    :is="`el-${child.type}`"
    :key="child.value"
    :label="child.value"
    v-bind="child.attrs"
  >
    {{ child.label }}
  </component>
</el-radio-group>

分组型组件的外层和内层都可能是动态组件,子节点类型不能写死,否则 radioradio-button 就没法共用同一套 schema。最常见的 bug 是外层渲染了 group,内层却没正确渲染子组件。

四、v-model 直绑 editorOptions 减少中间层

这个页面不是复杂表单提交,而是"配置项即改即生效",所以最直接的数据流是 Schema 表单直接改 editorOptions、编辑器立即响应。课程里更推荐 VForm v-model="editorOptions",而不是额外走一层 useForm() 再取 model。

Vue SFC
<VForm v-model="editorOptions" :schema="schema" />

调参面板的目标是"值直接驱动组件",减少中间层通常更直观。如果后续页面要演变成复杂提交表单,再考虑引入 useForm()v-model 直绑对象时,后面要特别注意深度监听与实例重建节奏。

五、响应式布局 ElRow + rowClass / rowStyle

给表单项加了 span 但布局没变成一行多列,根因在于:span 控制的是列宽,但前提是外层必须存在真正的栅格行容器。所以后续改造方向是在 VForm 外层补 ElRow,同时给 Schema 表单扩展 rowClassrowStyle

ts
export interface FormSchema {
  rowClass?: string
  rowStyle?: CSSProperties
  columnProps?: Record<string, any>
}

这一步不是"样式优化",而是动态表单能力边界的继续扩展——行级布局能力也应该作为可配置项暴露出来,让配置面板能排版成一行多列。

六、columnProps 断点布局

span 进一步升级成 columnProps,让 Schema 表单不再是固定 12 栅格,而是真正进入响应式断点布局(如 xs: 24sm: 12md: 8)。同一个配置面板在大屏一行显示多个项,中屏自动折成两行,小屏一项占满一行。

ts
{
  label: "模式",
  prop: "mode",
  type: "radio-group",
  columnProps: { xs: 24, sm: 12, md: 11 },
  children: [/* ... */],
}

columnProps 比单纯一个 span 更适合配置面板这种强响应式场景,本质是把 Element Plus 的栅格能力上提到 Schema 层。不同表单项尺寸差异大时,分配往往要多轮微调。

七、mode / lang / height 三类更新路径

示例页选的 modelangheight 很有代表性,分别对应三类不同更新路径:mode 是编辑器内部结构或渲染模式变化;lang 是需要重建实例才能彻底生效的语言切换;height 是纯尺寸类变化。一套面板就能覆盖三类不同更新路径,做演示页时优先选择差异明显的控制项,展示效果更直观。

八、深度监听新旧引用问题与 useDebounceFn 稳定重建

课程专门演示了一个容易误判的坑:给 watch(options, (newOptions, oldOptions) => {}) 再试图用 !==isEqual 比较新旧对象,结果发现要么拦不住、要么把响应式链路弄断。原因是在 Vue 深度监听场景下,如果你改的是同一个对象内部属性,那么 newOptionsoldOptions 往往指向同一个响应式对象引用,而不是两个快照——所以仅靠比较对象本身解决不了。

真正该想的是"如何控制更新节奏"。对会触发销毁重建的配置变化,用 useDebounceFn() 包一层比同步重建更稳,因为连续点击切模式/切语言/快速调高度时,每次都立刻 getValue -> destroy -> initEditor 很容易撞上竞态(旧实例没结束新实例就开始)。用 debounce 把快速变化合并成一次稳定更新:

ts
const debouncedResetEditor = useDebounceFn(() => {
  if (!editorInstance.value) return
  historyValue.value = editorInstance.value.getValue()
  editorInstance.value.destroy()
  initEditor(editorOptions.value)
}, 200)

watch(() => editorOptions.value, () => debouncedResetEditor(), { deep: true })

延迟不是越大越好,要在稳定性和交互实时性之间平衡(课程给了 200ms 到 50ms 的调参思路)。这里真正解决的是"高频配置变更导致的重建节奏失控",而不是对象比较不准确本身。


常见问题

问题原因解决方案
schema 写了 radio-group 却没渲染动态表单只支持单组件,没处理分组型在 VFormItem 补 group 结构渲染与 children 子项描述
写了 span 布局还是没变一行外层没有真正的行容器给 VForm 外层补 ElRow,并扩展 rowClass/rowStyle
切换语言/模式控制台报错配置变化过快,销毁重建发生竞态把重建逻辑包进 useDebounceFn
用 oldOptions!==newOptions 判断变化深度监听下新旧值可能是同一引用不依赖对象比较,直接按变化节流重建
窄屏下挤成一团只用了固定 span,没断点配置给 schema 增加 columnProps,按 xs/sm/md 分配宽度
快速加减高度变化太慢debounce 时间设置过大把延迟从 200ms 调到 50ms 或 100ms

延伸阅读