富文本编辑器框架slate.js
slate.js 是一个mvc框架,是通过数据操作视图的。
什么是slate.js的mvc呢?为什么要这么设计?
这里我们假设我们自己是一个写编辑器的人,我们需要哪些数据结构呢,我们需要哪些信息呢?
- 光标
- 内容对应的数据结构 只要有了这两个信息,那我们完全设计一款编辑器,这就是核心,所以slate.js也提供了这两个信息。
slate给我们提供的光标是这样的
{
anchor:{
path:number[],
offset:number
},
focus:{
path:number[],
offset:number
}
}
和document.getSelection()类似 anchor 是我们鼠标第一次点的位置,同理focus是我们鼠标松下的位置,这样的光标就给我们创造了无限的可能性,如果鼠标仅点击以下,那anchor就和focus相同,如果从后向前操作鼠标,那anchor和focus就是反向的,focus的位置在前面。
至于为什么会有path和offset,offset对应的文本,因为我们鼠标点击的区域可能不是一个段落,而path为什么是数组,因为会出现嵌套,比如,table,会嵌套tr,td,那表格中第一个文本的位置就应该是多层的,[0,0,0]第一个0对应table(默认编辑器只有一个table,那它就是第一个元素),第二个对应tr,第三个对应td。
自定义光标给我们提供了无限的希望,属于自己的光标才是完全受控的,我们不希望受到别人的干扰,不局限于浏览器,正如人生一样,最高的追求便是自由。
光标说完,那数据结构就是另一个关键,我们需要一个属于自己的数据库,属于自己的数据结构,如果有了它,那我们便是真正的自由。
slate.js给我们提供了他的数据结构。
[
{
type:'paragraph',
children:[
{text:'hello slate.js'}
]
}
]
没错它就是一个数组,看上去如此的平平无奇,好像在我们心中,它就是应该长成这个样子。
我们现在定义了一个段落,段落中包括一个儿子,他是一个text文本节点,信息是hello slate.js
,我想现在在我们脑子里面,一眼望上去,他就是<p>hello slate.js</p>,没错,它对应的html就应该是这个。
下面我们来分析它提供的到底是什么数据结构,首先最外层是一个数组,原因是一个富文本编辑器会在最外层有很多元素,他和vue2不同,只能有一个根节点,他可以有两个段落,所以它最外层是数组。
这就是第一点,它是个数组
我们来看第二点
我们发现他的元素要有{type:string,children:[]},这是为什么?
其实想一想就知道了,因为富文本编辑器要有块级元素的定义,什么是块级元素,它是一个div,它是一个p标签,它是一个table,它是自己一个人一个块,它可以有属于自己的span,tr,和文本,那么什么是span,tr呢,一个元素,它还是一个元素,它还可以有自己的子元素,子元素可以是文本,可以是其他元素td,所以他们的结构应该是{type:string,children:[]},那什么是文本呢,文本就是{text:string},所以结构一下子就在你脑中出来了,原来如此,原来是这么设计的。可能这时有个疑问,如果我是空span呢,那结构可以是这样的{type:'span',children:[{text:''}]},那我有一个子元素,让他是空字符串不就好了。
这就是第二点,只要不是文本节点它的结构一定是{type:string,children:array}
无限的可能性
好像编辑器有了这两个东西就够了,所以slate.js做了什么,既然你也说了数据结构就这么简单,那为什么还要用slate.js,换句话说slate.js做了什么操作呢,就这数据结构,这我上我也行?
第一点 :提供了api方便操作 提供了Editor,Transform,Range等api方便使用 Editor.insertText() 用这个api,就会插入文本了,直接就能帮我们操作数据结构了,然后写入{text:'xxx'}。
你可能会说就这,那我写一个不也行吗。请接着看
第二点: immer,slate.js使用了immer,为什么要用immutable.js或者immer这种库呢,很多用react的人就会说了,immer和immutable.js给我们提供了不可变数据,没错,不可变数据就是核心,编辑器的数据一定要是不可变的,我们必须要保持数据的唯一性。
第三点:normalize slate.js给我们提供相当多的格式化操作,比如当合并操作,当出现了{text:'agsgs'},{text:'www'},那么slate就会帮助我们自动合并,{text:'agsgswww'}
第四点:原子操作 这里我指的是,slate会记录你每一步的操作
{
path: (2) [0, 0]
text: "1"
type: "insert_text"
offset: 0
}
比如我输入一个1,那么他就会自动给我把insertText转换成这个对象 那么既然有这个对象,就有和他相对的对象,这样如果我们需要回退的时候,我们就可以使用一个类似于reverse(),来进行回退。 所以它在底层给我们支持了撤销回退的机制。
所以slate.js是什么呢?它是给我们提供了光标和数据结构,给我提供格式化和原子操作的mvc的框架,有了它,写编辑器也就有了无限的可能。