如何运用OT-JSON与Immer构建适用于场景的低代码富文本编辑器状态管理策略?

摘要:在复杂应用中,例如低代码、富文本编辑器的场景下,数据结构的设计就显得非常重要,这种情况下的状态管理并非是redux、mobx等通用解决方案,而是需要针对具体场景进行定制化设计,那么在这里我们来尝试基于Immer以及OT-JSON实现原子化、
在复杂应用中,例如低代码、富文本编辑器的场景下,数据结构的设计就显得非常重要,这种情况下的状态管理并非是redux、mobx等通用解决方案,而是需要针对具体场景进行定制化设计,那么在这里我们来尝试基于Immer以及OT-JSON实现原子化、可协同、高扩展的应用级状态管理方案。 描述 将Immer与OT-JSON结合的想法来自于slate,我们首先来看一下slate的基本数据结构,下面的例子是高亮块的描述。这个数据结构看起来非常像零代码/低代码的结构,因为其含有很多children,而且存在对节点的装饰描述,即bold、border、background等属性值。 [ { "highlight-block": { border: "var(--arcoblue-6)", background: "var(--arcoblue-3)", }, children: [ { children: [{ text: "🌰 " }, { text: "举个栗子", bold: true }] }, { children: [{ text: "支持高亮块 可以用于提示文档中的重要内容。" }] }, ], }, ]; 那么这里的设计就很有趣,之前的文章中我们就聊过,本质上低代码和富文本都是基于DSL的描述来操作DOM结构,只不过富文本主要是通过键盘输入来操作DOM,而无代码则是通过拖拽等方式来操作DOM,这里当然是有些共通的设计思路,这个结论其实就是来自于slate的状态管理。 本文实现的相关DEMO都在https://github.com/WindRunnerMax/webpack-simple-environment/tree/master/packages/immer-ot-json中。 基本原则 前边我们也提到了数据结构的具体场景进行定制化设计,这部分主要指的是JSON的结构非常灵活,像是高亮块的描述,我们可以将其设计为单独的对象,也可以将其拍平,以Map的形式来描述节点的装饰,再例如上述文本内容则规定了需要用text属性描述。 原子化的设计非常重要,在这里我们将原子化分为两部分,结构的原子化与操作的原子化。结构的原子化意味着我们可以将节点自由组合,而操作的原子化则意味着我们可以通过描述来操作节点状态,这两者的组合可以方便地实现组件渲染、状态变更、历史操作等等。 节点的自由组合可以应用在很多场景中,例如表单结构中,任何一个表单项都可以都可以变为其他表单项的嵌套结构,组合模式可以设定部分规则来限制。操作的原子化可以更方便地处理状态变更,同样是在表单中,嵌套的表单项展开/折叠状态就需要通过状态变更实现。 当然,原子化执行操作的时候可能并没有那么理想,组合ops来执行操作表达类似action范式的操作也是很常规的操作,这部分就是需要compose的处理方式。并且状态管理可能并不是都需要持久化,在临时状态管理中,client-side-xxx属性处理很容易实现,AXY+Z值处理则会更加复杂。 协同算法的基础同样是原子化的操作,类似于redux的范式action操作非常方便,但是却无法处理协同冲突,同样也不容易处理历史操作等。这一局限性源于其单向、离散的操作模型,每个action仅表达独立意图,而缺乏对全局状态因果关系(操作A影响操作B状态)的显式维护。 OT-JSON则可以帮助我们将原子化的操作,扩展到协同编辑的复杂场景中,通过引入操作变换OT,以此来解决冲突。当然仅仅是前端引入操作变换是不够的,还需要引入后端的协同框架,例如ShareDB等。当然,CRDT的协同算法也是可行的选择,这属于应用的选型问题了。 此外,OT-JSON天然可以支持操作历史的维护,每个操作都携带了足够的上下文信息,使得系统能够追溯状态变化的完整链条,为撤销/重做、版本回溯等高级功能提供了基础。操作之间的因果关系也被显式地记录下来,使得系统能够做到操作A必须在操作B之前应用这样的约束条件。 扩展性这部分的设计可以是比较丰富的,,树形结构天然适合承载嵌套式数据交互。例如飞书文档的各种模块,都是以Blocks的形式扩展出来的。恰好飞书的数据结构协同也是使用OT-JSON来实现的,文本的协同则是借助了EasySync作为OT-JSON的子类型来实现的,以此来提供更高的扩展性。 当然,扩展性并不是说可以完全自由地接入插件,插件内的数据结构还是需要整体接受OT-JSON的调度,并且文本这种特殊的子类型也要单独调度。以此系统框架能够将各种异构内容模块统一纳入协同体系,并且可以实现统一的状态管理、协同编辑、历史记录等功能。
阅读全文