官方的插件系统
温馨提示:本文需要您对slatejs有一定了解
首先,官方提供的插件使用方法示例如下:
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
复制代码
即:在把createEditor()
返回的editor实例作为参数传入插件内,插件对其属性、方法进行包装,最终再把包装后的editor实例return出去
slate官方提供的插件机制存在以下问题
- 插件较多时,会有很多层级的嵌套,不优雅
- 没有统一的插件管理器
- 每一个插件都会对传入的editor进行包装,再返回包装后的editor,可能存在多个插件修改了editor上的同一个方法,导致最终管理起来比较复杂
实现自己的插件系统
既然要做自己的插件系统,那肯定是希望避免官方存在的问题,即达到如下效果:
- 避免插件嵌套,采用插件挂载的方式,传入什么插件,就能用什么插件
- 做一个统一的插件管理器,统一管控插件行为
- 插件内部自己实现自己的方法,调用时根据需要,由插件管理器调用插件内部方法,
希望达成的效果
伪代码如下:
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实例上,供后续使用。