1、前言

wangEditor 5 曾经公测有一段时间了,在公测群里常常有人问一些官网提供的 vue 组件相干的问题,因而我从应用这角度总结了一下以后 @wangeditor/editor-for-vue@next 存在的一些缺点:

  • editorId 的设定让咱们能拿到编辑器的实例,尽管达到了最终成果,但应用起来并不是怎么高效,也不够傻瓜。并且须要在编辑器销毁后应用 editorId 来手动革除缓存,用户如果遗记了这一步就会造成内存透露。
  • 异步设置内容时须要额定的变量来管制编辑器的创立,减少了应用的复杂度,同样不够傻瓜
  • defaultContent 必须是深度克隆的数据,且把这一操作交给了用户,减少应用复杂度
  • 不反对 v-model,把数据的同步推给了用户,减少应用复杂度
  • vue 是一个反对双向绑定的框架,而咱们的组件库的一些非凡配置项并不是响应式的,如果用户能通过 editable.config.readOnly = false 就能够禁用编辑器,应用 editable.mode = 'simple' 即可切换编辑器模式,那么将为用户省略 n 多行代码,应用起来更傻瓜
  • 对事件的解决,明明能够间接应用 config.onChange = () => {} 却硬要独自提出来,这一点我不是很了解
  • 非凡场景:在一个页面内,左侧是文章列表,右侧是编辑器,点击列表中的文章,编辑器主动显示文章内容且不会有历史记录(之前 QQ 群中一用户的需要)。针对这种状况,用户只能一直的管制某个变量,给变量先赋值 false 再赋值 true 来销毁重建编辑器,如果能为用户提供一个 reloadEditor API 将使应用更简略

针对下面的缺点,在 21/12/21 这天我开始尝试本人封装一个自认为好用的 vue3 组件。到 21/12/30 这天算是全面竣工。

该组件反对的性能有:

  • 反对动静配置编辑器参数(编辑器创立后批改配置项任失效)
  • 反对 v-modelv-model:html 两种模式的双向绑定
  • 反对动态显示默认内容而不会存在旧文档的历史记录
  • 同时默认内容的配置项反对 json arrayjson stringhtml string 三种格局的数据
  • 人造反对 TypeScript

因为个中原因,目前不公布 npm 包,如有须要能够 GitHub 自取(仅一个文件),如果感觉好用无妨给个 star

2、自封组件的应用

2.1、全局注册组件

import { createApp } from 'vue'import wangeditor from 'xxx/wangeditor'// 全局注册 EditorToolbar, EditorEditable 两个组件createApp(App).use(wangeditor).mount('#app')

2.2、疾速开始

<style lang="scss">  .border {    border: 1px solid #ddd;  }</style><template>  <editor-toolbar class="border" :option="toolbar" @reloadbefore="onToolbarReloadBefore" />  <editor-editable    class="border"    :option="editable"    v-model="formData.json"    v-model:html="formData.html"    @reloadbefore="onEditableReloadBefore"  /></template><script lang="ts">  import { Descendant } from 'slate'  import {    EditorEditable,    EditorEditableOption,    EditorToolbar,    EditorToolbarOption,    useWangEditor,  } from 'xxx/wangeditor'  import { defineComponent, shallowReactive } from 'vue'  export default defineComponent({    components: { EditorToolbar, EditorEditable },    setup() {      // 编辑器配置      const editableOption: EditorEditableOption = {}      // 菜单栏配置      const toolbarOption: EditorToolbarOption = {}      // 防抖时长。当会触发重载的配置项发生变化 365ms 后,编辑器会重载      const reloadDelary = 365      const { editable, toolbar, getEditable, getToolbar, clearContent, reloadEditor } = useWangEditor(        editableOption,        toolbarOption,        reloadDelary      )      // 开启只读模式      editable.config.readOnly = true      // 不要应用 reactive/ref,应该应用 shallowReactive/shallowRef 来接管 json 数据      const formData = shallowReactive({        json: [] as Descendant[],        html: '',      })      function onEditableReloadBefore(inst: IDomEditor) {        console.log('editable 行将重载: ' + new Date().toLocaleString())      }      function onToolbarReloadBefore(inst: Toolbar) {        console.log('toolbar 行将重载: ' + new Date().toLocaleString())      }      return { editable, toolbar, formData, onEditableReloadBefore, onToolbarReloadBefore }    },  })</script>

2.3、Vue hook: useWangEditor

通过 useWangEditor 解决后,返回的 editabletoolbar 别离对应编辑器菜单栏的配置项,不过此时的配置项对象具备了响应式个性,咱们能够间接批改 editable/toolbar 对应属性来 更新重载 编辑器。

如果传入的 editableOptiontoolbarOption 是响应式数据,外部将主动解除与之前的关联,也就意味着通过 useWangEditor 解决后失去的 editabletoolbar 配置对象,即便内容发生变化也不会触发之前的依赖更新!!!

/** * vue hook,用于实现编辑器配置项的动静绑定 * @param {Object} editableOption 编辑器主体局部的配置 * @param {Object} toolbarOption 菜单栏配置 * @param {Number} reloadDelay 防抖时长,用于重载的提早管制,单位:毫秒 */declare function useWangEditor(  editableOption: EditorEditableOption | null = null,  toolbarOption: EditorToolbarOption | null = null,  reloadDelay: number = 365): {  editable: Required<EditorEditableOption>  toolbar: Required<EditorToolbarOption>  getEditable: () => IDomEditor | undefined  getToolbar: () => Toolbar | undefined  clearContent: () => void  reloadEditor: () => void}

2.3.1、配置项:EditorEditableOption

/** * 编辑器配置项 */interface EditorEditableOption {  /** 编辑器模式 */  mode?: 'default' | 'simple'  /** 编辑器初始化的默认内容 */  defaultContent?: Descendant[] | string | null  /** 编辑器配置,具体配置以官网为准 */  config?: Partial<IEditorConfig>  /** v-model/v-model:html 数据同步的防抖时长,默认值:3650,单位:毫秒 */  delay?: number  /**   * 编辑器创立时默认内容的优先级排序,默认值:true。   * true:v-model > v-model:html > defaultContent。   * false: defaultContent > v-model > v-model:html。   */  extendCache?: boolean}

2.3.2、配置项:EditorToolbarOption

/** * 菜单栏的配置项 */interface EditorToolbarOption {  mode?: 'default' | 'simple'  config?: Partial<IToolbarConfig>}

2.4、动静批改配置

const { editable, toolbar } = useWangEditor()editable.config.placeholder = '新的 placeholder'// 切换为只读模式editable.config.readOnly = truetoolbar.mode = 'simple'

2.4.1、数据优先:EditorEditableOption.extendCache

v-model/v-model:htmldefaultContent 同时应用的时候,咱们能够应用 extendCache 配置项来管制重载后编辑器的默认内容。

extendCahcetrue 时,编辑器创立/重载时显示内容的优先级为:v-model > v-model:html > defaultContent

extendCachefalse 时,编辑器创立/重载时显示内容的优先级为:defaultContent > v-model > v-model:htmlfalse 模式下可能会造成数据的失落,因而在编辑器重载前肯定要做好数据的保留工作,咱们能够配置 reloadbefore 事件来进行数据的保留。

2.4.2、默认值:EditorEditableOption.defaultContent

defaultContent 的变更默认状况下是不会触发编辑器的重载的,如果须要将 defaultContent 内容间接显示进去,咱们须要通过 reloadEditor 来强制重载编辑器。并且咱们须要留神 extendCache 对重载后编辑器默认内容的影响。

const { editable, toolbar, reloadEditor } = useWangEditor()onMounted(() => {  setTimeout(() => {    // 当你进行了 v-model/v-model:html 绑定时,如果你想在编辑器重载后将新设    // 置的默认值显示为编辑器的默认内容,那么你须要设置 extendCache 为 false,    // 这会导致编辑器内容的失落,能够正当搭配 reloadbefore 事件进行解决    editable.extendCache = false    // 而后再批改配置    editable.defaultContent = [{ type: 'header1', children: [{ text: '题目一' }] }]    // 同时还反对字符串模式的 JSON    editable.defaultContent = '[{"type":"header1","children":[{"text":"题目一"}]}]'    // 针对 HTML 字符串也做了兼容(不举荐应用,有缺点)    editable.defaultContent = '<h1>题目一</h1><p>段落</p>'    // 最初,你还须要强制重载编辑器    reloadEditor()  }, 5000)})

2.5、编辑器/菜单栏 重载

EditorEditableOption.modeEditorEditableOption.config.hoverbarKeysEditorEditableOption.config.maxLengthEditorEditableOption.config.customPaste 这几个配置项的变更会触发编辑器的重载,其它的 EditorEditableOption 配置项仅反对动静配置,但并不会触发重载,这能防止不必要的资源耗费。如果你须要强制重载编辑器,还提供了 reloadEditor API 来供使用者手动触发。

EditorEditableOption 不同的是,EditorToolbarOption 的的任意选项发生变化,都会触发菜单栏的重载。

const { reloadEditor } = useWangEditor()// 强制重载编辑器reloadEditor()

2.5.1、重载之前:reloadbefore 事件

在编辑器重载之前,会触发 reloadbefore 事件。

<template>  <editor-toolbar :option="toolbar" @reloadbefore="onToolbarReloadBefore" />  <editor-editable v-model="formData.json" :option="editable" @reloadbefore="onEditableReloadBefore" /></template><script lang="ts">  import axios from 'axiios'  import { Descendant } from 'slate'  import { EditorEditable, EditorToolbar, useWangEditor } from 'xxx/wangeditor'  import { defineComponent, shallowReactive } from 'vue'  export default defineComponent({    components: { EditorToolbar, EditorEditable },    setup() {      const { editable, toolbar, reloadEditor } = useWangEditor()      const formData = shallowReactive({        json: [] as Descendant[],      })      function onEditableReloadBefore(inst: IDomEditor) {        window.alert('editable 行将重载')        console.log('editable 行将重载: ' + new Date().toLocaleString())        // 提交数据        axios.post('xxx/xxx', formData)      }      function onToolbarReloadBefore(inst: Toolbar) {        window.alert('toolbar 行将重载')        console.log('toolbar 行将重载: ' + new Date().toLocaleString())      }      return { editable, toolbar, formData, onEditableReloadBefore, onToolbarReloadBefore }    },  })</script>

2.6、革除内容

不仅会革除编辑器内容,还会同步 v-model/v-model:html 数据

const { clearContent } = useWangEditor()clearContent()

2.7、获取菜单栏实例

const { getToolbar } = useWangEditor()const toolbarInstance: Toolbar | undefined = getToolbar()if (toolbarInstance) {  // do somthing} else {  // do somthin}

2.8、获取编辑器实例

const { getEditable } = useWangEditor()const editableInstance: IDomEditor | undefined = getEditable()if (editableInstance) {  console.log(editableInstance.children)} else {  console.error('编辑器未实例化')}

2.9、对于对 v-model 的反对

EditorEditable 同时反对 v-modelv-model:html 两种模式的数据绑定,别离对应 json arrayhtml string 两种格局的数据。两种格局能够同时绑定,也能够独自只绑定 v-modelv-model:html 之一,亦能够不进行数据绑定。

不举荐只进行 v-model:html 绑定,有无奈防止的缺点!!! 并且须要留神 extendCache 可能存在的影响!!!

同时,当咱们进行 v-model 绑定时,举荐应用 shallowReactive/shallowRef 来缓存 json array 数据。如果你执意应用 reactive/ref 进行数据缓存,那么在运行时呈现未知谬误,那么你可能找不到问题所在。重要!重要!!重要!!!

<template>  <editor-editable :option="editable" v-model="formData.json" v-model:html="formData.html" /></template><script lang="ts">  import { Descendant } from 'slate'  import { useWangEditor } from '@we/wangeditor'  import { defineComponent, shallowReactive } from 'vue'  export default defineComponent({    setup() {      const { editable } = useWangEditor()      const formData = shallowReactive({        json: [] as Descendant[],        html: '',      })      return { editable, formData }    },  })</script>

<template>  <editor-editable :option="editable" v-model="jsonData" v-model:html="htmlData" /></template><script lang="ts">  import { Descendant } from 'slate'  import { useWangEditor } from '@we/wangeditor'  import { defineComponent, shallowRef } from 'vue'  export default defineComponent({    setup() {      const { editable } = useWangEditor()      const jsonData = shallowRef<Descendant[]>([])      const htmlData = ref('')      return { editable, jsonData, htmlData }    },  })</script>

3、总结

此次封装组件给我最大的感触莫过于:成也响应式 API,bug 也来自响应式 API。因而,如果你也在应用第三方库,且这些库中应用到了响应式数据,那么肯定要正当应用 toRawmarkRawshallowReactiveshallowReadonlyunrefshallowRef 这些 API 来解除数据的响应式个性。否则,运行时 bug 可不是那么好解决的。

针对响应式个性造成的运行时 bug,wangEditor 官网给出的解决示例(issues:262)是对数据进行深度克隆,尽管不够优雅,但成果却是分外的好用,也足够简略。当然,在本组件中只有你依照文档来,就不必思考相干问题。

4、最初

因为个中原因,目前不公布 npm 包,如有须要能够 GitHub 自取(仅一个文件),如果感觉好用无妨给个 star