让我们继续探究富文本编辑器ProseMirror。
DirectEditorProps
ProseMirror文档在Web页面上的呈现是通过视图来管理的。其中一个核心的类是EditorView,一个例子:
let appState = {
editor: EditorState.create({schema}),
score: 0
}
let view = new EditorView(document.body, {
state: appState.editor,
dispatchTransaction(transaction) {
update({type: "EDITOR_TRANSACTION", transaction})
}
})
还记得上一篇介绍的,ProseMirror的文档是用EditorState来控制,然后通过[Transaction]来更新的吗?
创建EditorView的时候必须指定文档状态(就是上面例子中的state:appState
属性),也可以挂载一个钩子dispatchTransaction
,用来截获transaction,做一些中间操弄。
如果你对ReactJS有所了解的话,你一定会对state
和props
这两个名词比较熟悉。ReactJS用state
来存储内部数据的状态,而用props
存储视图的一些属性。ProseMirror或多或少借鉴了这个开年。上文中创建EditorView时提到的state
和dispatchTransaction
都是属于DirectEditorProps集合内的参数。
而DirectEditorProps则是EditorProps的超集。EditorProps中定义了一系列属性用于DOM相关的和操作,比如事件处理器、剪贴板解析等等。ProseMirorr的Plugin可以提供EditorProps中的属性,从而影响ProseMirorr对文档和视图的操作。
但是这样一来,EditorView自身的DirectEditorProps中的属性,以及Plugin集合提供的EditorProps中的属性有重合的地方,到底依据哪个来决定视图的行为呢?EditorView提供了一个someProp方法,可以按次序(先EditorView然后依次注册的Plugin集合)以一定的逻辑(不同属性逻辑不同)遍历Props集合,来决定视图的行为。
DOM生成
ProseMirror的文档是一颗节点树,在生成视图的时候就要把这些节点树转化为相应的DOM结构。但是,ProseMirror并没有直接把文档节点树转化为DOM结构,而是在中间加了一层ViewDesc。
创建ViewDesc需要使用以下参数:
- parent, 父ViewDesc
- children, 子ViewDesc
- dom, 该ViewDesc所对应的DOM节点
- contentDOM,见下文
ViewDesc.dom
含有一个pmViewDesc
属性,指向该ViewDesc本身。contentDOM
用于告诉ProseMirror在何处插入子ViewDesc。contentDOM
和dom
可以是同一个DOM节点。
从ViewDesc派生出若干个子类,包括:
- NodeViewDesc
- MarkViewDesc
- TextViewDesc
其中NodeViewDesc就是用来描述文档中的Node,它的子类TextViewDesc用来表述文档中的TextNode。MarkViewDesc用来描述TextNode所附带的Mark集合。值得注意的是,虽然在文档中,Mark集合是TextNode(或者其他inline内容)的属性,不参与到树形结构的构成,但是在ViewDesc中,由于是要和DOM树同步,所以MarkViewDesc参与树形结构的构成。
假如有下面一个文档:
- paragraph
- text (内容:“Hello, “)
- text (内容:“world!",标记:strong)
转化为ViewDesc后,会变成:
- NodeViewDesc (对应paragraph)
- TextViewDesc (对应"Hello, “)
- MarkViewDesc (对应strong)
- TextViewDesc (对应"world!")
小结
其实我们可以把ViewDesc理解为VirtualDOM。ProseMirror通过ViewDesc来保持文档和DOM之间的同步。具体同步的过程如下所示:
DOM event
↗ ↘
EditorView Transaction
↖ ↙
new EditorState
ViewDesc决定了如何响应DOM事件,并形成相应的Transaction;另外当EditorState更新之后,ViewDesc又通过更新的EditorState来更新DOM结构。所以ViewDesc具有双向同步能力,从DOM中同步出ProseMirorr文档,反之亦然。