富文本编辑器踩坑——(一)slatejs插件管理系统

官方的插件系统

温馨提示:本文需要您对slatejs有一定了解

首先,官方提供的插件使用方法示例如下:

const editor = useMemo(() => withHistory(withReact(createEditor())), [])
复制代码

即:在把createEditor()返回的editor实例作为参数传入插件内,插件对其属性、方法进行包装,最终再把包装后的editor实例return出去

slate官方提供的插件机制存在以下问题

  1. 插件较多时,会有很多层级的嵌套,不优雅
  2. 没有统一的插件管理器
  3. 每一个插件都会对传入的editor进行包装,再返回包装后的editor,可能存在多个插件修改了editor上的同一个方法,导致最终管理起来比较复杂

实现自己的插件系统

既然要做自己的插件系统,那肯定是希望避免官方存在的问题,即达到如下效果:

  1. 避免插件嵌套,采用插件挂载的方式,传入什么插件,就能用什么插件
  2. 做一个统一的插件管理器,统一管控插件行为
  3. 插件内部自己实现自己的方法,调用时根据需要,由插件管理器调用插件内部方法,

希望达成的效果

伪代码如下:

const plugins = [new PluginA(), new PluginB(), new PluginC()];
 <Editor plugins={plugins} />
复制代码

简而言之,我们希望自定义插件可以是一个个的Class,在使用时直接new出需要的插件,然后作为参数传入Editor组件内,这便是我们需要实现的目标

思路

要达到我们想要的效果,首先需要自己去抹平自定义插件和官方插件的差异,所以我们需要一个插件管理器

插件管理器的实现

插件管理器与官方插件一致,传入一个editor实例,对editor实例进行包装,返回包装后的editor实例;而插件则是Class,继承自basePlugin。

export interface BasePlugin {
  isVoid?: boolean;
  isInline?: boolean;
  editor: Editor | null;
  pluginType: string;
  renderElement?: (props: RenderElementProps) => JSX.Element;
  apply?: (op: Operation) => void;
}
复制代码

伪代码如下:

const withPlugins = (editor: Editor) => {
    const {apply, isVoid, isInline} = editor
    editor.plugins = [];
    editor.initPlugins = (plugins) => {
        //给所有的插件注入editor
        plugins.map(plugin => plugin.editor = editor)
        editor.plugins = plugins
        editor.pluginMounted = true;
    }
    
    editor.isVoid = (element) => {
        const matchedPlugin = editor.plugins.find(plugin => plugin.pluginType === element.type)
        if (matchedPlugin && typeof matchedPlugin.isVoid === 'boolean') {
          return matchedPlugin.isVoid;
        }
        return isVoid(element);
    }
    //思路和isVoid相同,先匹配对应类型的插件,找到插件后如果插件中有isInline属性,则返回插件的,否则使用slate自身的方法去判断
    editor.isInline = xxx
    
    //renderElement的思路:先通过元素类型匹配插件,匹配到了插件后使用插件的renderElement去渲染元素,否则是有slate自带的DefaultElement去渲染
    editor.renderElement = (props: RenderElementProps) => {
        const matchedPlugin = editor[SYM_PLUGINS].find((plugin) => plugin.pluginType === props.element.type);
        if (matchedPlugin && matchedPlugin.renderElement) {
            return matchedPlugin.renderElement(props);
        }
        return DefaultElement(props);
    };
    
    editor.apply = (op: Operation) => {
        editor.plugins.map((plugin) => plugin.apply?.(op));
        apply(op);
    };
    return editor
}
复制代码

一个简单的插件管理器就实现了,但是需要配合封装后的Editor来使用,伪代码如下:

const MyEditor = ({
    plugins,
    value,
    onChange,
}) => {
    const editor = useMemo(() => withPlugins(withReact(withHistory(createEditor()))), []);
    if (!editor.pluginsMounted) {
        editor.initPlugins(plugins);
    }
    
    return (
      <Slate
        editor={editor}
        value={value}
        onChange={(newValue) => {
          onChange(newValue);
        }}
      >
        <Editable
          renderElement={editor.renderElement}
          placeholder={'输入点内容试试吧'}
        />
      </Slate>
    );
}
复制代码

此时,在外层调用已经可以达成我们最初想要的效果了。

总结

本文核心思路是通过官方提供的插件方式,实现一个插件管理器,统一管控自定义插件,且作为桥梁在插件管理器和自定义插件之间通信;对Editor组件进行简单封装,使Editor组件可以接受plugins数组,并挂载到editor实例上,供后续使用。

Guess you like

Origin juejin.im/post/7032569154527985695