经过前面几个章节的挖掘,已经掌握了一些信息,此篇是要回头看一下CodeMirror中的EditorView。
export class EditorView {
get state() { return this.viewState.state }
get viewport(): {from: number, to: number} { return this.viewState.viewport }
get visibleRanges(): readonly {from: number, to: number}[] { return this.viewState.visibleRanges }
get inView() { return this.viewState.inView }
get composing() { return this.inputState.composing > 0 }
get compositionStarted() { return this.inputState.composing >= 0 }
constructor(config: EditorViewConfig = {}) {
// ...
this.viewState = new ViewState(EditorState.create(config))
// ...
}
}
从上面的代码可以看到,EditorView里面很多讯息都是从ViewState里面拿到的,而ViewState是通过配置创建的状态得到的。
在CodeMirror中,有些状态是特定于视图的,比如滚动位置、视窗尺寸和是否滚动到底部等等。CodeMirror将这些信息由ViewState来管理,而不是存储在EditorState中。这样做的原因是为了将视图逻辑从状态管理中分离出来,使得EditorState可以专注于表示编辑器的状态,而EditorView则负责根据ViewState提供的信息来渲染UI。
ViewState负责执行与视图相关的计算,比如确定哪些部分的文档应该在屏幕上渲染,以及如何根据当前的滚动位置和视窗尺寸来优化这些计算。它缓存这些计算结果,以便快速响应视图更新。因此CodeMirror可以仅更新视图中变化的部分,而不是整个文档。减少了不必要的计算和渲染,提高了性能。
dispatch之后发生了什么?
第一章节中讨论过这个流程,使用EditorView实例去dispatch事务之后,事务里面的state会同步到这个EditorView实例上。
export class EditorView {
constructor() {
// 精简了一些逻辑
this.dispatchTransactions = trs => this.update(trs)
}
// ...
dispatch(...input: (Transaction | readonly Transaction[] | TransactionSpec)[]) {
// ...
this.dispatchTransactions(trs, this)
}
}
再精简一下
export class EditorView {
// ...
dispatch(...input: (Transaction | readonly Transaction[] | TransactionSpec)[]) {
// ...
this.update(trs)
}
}
可以看到,dispatch事务的时候,最终就是在调用update方法。
EditorView的update方法是编辑器视图更新的核心,它负责将事务应用到当前的编辑器状态,并更新视图以反映这些变化。
update(transactions: readonly Transaction[]) {
// ... 确保没有正在进行的更新,否则抛出错误
// redrawn(是否有重绘)、attrsChanged(属性是否变化)
let redrawn = false, attrsChanged = false, update: ViewUpdate
// 当前状态
let state = this.state
// ...
// 检查焦点状态是否变化,并设置焦点标志
let focus = this.hasFocus, focusFlag = 0, dispatchFocus: Transaction | null = null
if (transactions.some(tr => tr.annotation(isFocusChange))) {
this.inputState.notifiedFocused = focus
// If a focus-change transaction is being dispatched, set this update flag.
focusFlag = UpdateFlag.Focus
} else if (focus != this.inputState.notifiedFocused) {
this.inputState.notifiedFocused = focus
// Schedule a separate focus transaction if necessary, otherwise
// add a flag to this update
dispatchFocus = focusChangeTransaction(state, focus)
if (!dispatchFocus) focusFlag = UpdateFlag.Focus
}
// 如果有挂起的DOM更改,立即读取并尝试应用
let pendingKey = this.observer.delayedAndroidKey, domChange: DOMChange | null = null
if (pendingKey) {
// ...
} else {
// 清除观察者
this.observer.clear()
}
if (state.facet(EditorState.phrases) != this.state.facet(EditorState.phrases))
return this.setState(state)
// 根据当前状态和事务创建ViewUpdate对象。
update = ViewUpdate.create(this, state, transactions)
update.flags |= focusFlag
// 处理事务中的滚动目标,如 scrollIntoView 效果。
let scrollTarget = this.viewState.scrollTarget
try {
// 设置更新状态为更新中
this.updateState = UpdateState.Updating
for (let tr of transactions) {
// ...
}
// 更新视图状态
this.viewState.update(update, scrollTarget)
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes)
if (!update.empty) {
// 更新插件
this.updatePlugins(update)
// 更新输入状态
this.inputState.update(update)
}
// 更新文档视图
redrawn = this.docView.update(update)
// 重新挂载样式
if (this.state.facet(styleModule) != this.styleModules) this.mountStyles()
// 更新DOM属性
attrsChanged = this.updateAttrs()
this.showAnnouncements(transactions)
this.docView.updateSelection(redrawn, transactions.some(tr => tr.isUserEvent("select.pointer")))
} finally { this.updateState = UpdateState.Idle }
if (update.startState.facet(theme) != update.state.facet(theme))
this.viewState.mustMeasureContent = true
// 请求测量以确保视图正确更新
if (redrawn || attrsChanged || scrollTarget || this.viewState.mustEnforceCursorAssoc || this.viewState.mustMeasureContent)
this.requestMeasure()
// 更新文档视图
if (redrawn) this.docViewUpdate()
if (!update.empty) {
for (let listener of this.state.facet(updateListener)) {
listener(update)
}
}
// Promise.resolve()用于确保后续代码在当前执行栈清空后执行,即微任务队列中
if (dispatchFocus || domChange) Promise.resolve().then(() => {
if (dispatchFocus && this.state == dispatchFocus.startState) this.dispatch(dispatchFocus)
if (domChange) {
if (!applyDOMChange(this, domChange) && pendingKey!.force)
dispatchKey(this.contentDOM, pendingKey!.key, pendingKey!.keyCode)
}
})
}
update方法里面考虑了一系列类型的变化:文档内容变化、选择(Selection)变化、视图状态变化、装饰(Decorations)变化、插件定义的状态变化、DOM属性变化、焦点状态变化、滚动目标变化、样式变化。然后根据这些变化请求重新测量并重绘视图。
每当更新时,无论是由于文档更改、选择更改还是其他原因,都会创建一个ViewUpdate实例,来收集产生的变化。
// ViewUpdate 类描述了编辑器视图更新的情况。
export class ViewUpdate {
// 此更新所做的文档更改
readonly changes: ChangeSet
// 更新前的编辑器状态
readonly startState: EditorState
// 内部标志,用于标记更新的特定方面
flags = 0
// 内部使用,存储由此更新更改的文档范围
changedRanges: readonly ChangedRange[]
// 私有构造函数,用于初始化 ViewUpdate 实例
private constructor(
// 与更新关联的编辑器视图
readonly view: EditorView,
// 更新后的新编辑器状态
readonly state: EditorState,
// 参与此更新的事务,可能为空
readonly transactions: readonly Transaction[]
) {
// 初始化开始状态为当前视图的状态
this.startState = view.state
// 初始化更改集为空,然后根据事务逐步构建
this.changes = ChangeSet.empty(this.startState.doc.length)
for (let tr of transactions) this.changes = this.changes.compose(tr.changes)
// 收集所有更改的范围。
let changedRanges: ChangedRange[] = []
this.changes.iterChangedRanges((fromA, toA, fromB, toB) => changedRanges.push(new ChangedRange(fromA, toA, fromB, toB)))
this.changedRanges = changedRanges
}
// 用于创建ViewUpdate实例
static create(view: EditorView, state: EditorState, transactions: readonly Transaction[]) {
return new ViewUpdate(view, state, transactions)
}
// 检查此更新是否改变了视口或可见范围
get viewportChanged() {
return (this.flags & UpdateFlag.Viewport) > 0
}
// 检查此更新是否改变了编辑器中某个区块元素的高度
get heightChanged() {
return (this.flags & UpdateFlag.Height) > 0
}
// 检查此更新是否改变了文档或编辑器的大小,或编辑器内元素的大小
get geometryChanged() {
return this.docChanged || (this.flags & (UpdateFlag.Geometry | UpdateFlag.Height)) > 0
}
// 检查此更新是否产生焦点变化
get focusChanged() {
return (this.flags & UpdateFlag.Focus) > 0
}
// 检查此更新是否更改了文档
get docChanged() {
return !this.changes.empty
}
// 检查此更新是否明确设置了选择范围
get selectionSet() {
return this.transactions.some(tr => tr.selection)
}
// 检查此更新是否为空,即没有发生任何变化
get empty() { return this.flags == 0 && this.transactions.length == 0 }
}
有了这个ViewUpdate,就可以同步viewState、inputState等等。
// ...
// 更新视图状态
this.viewState.update(update, scrollTarget)
this.bidiCache = CachedOrder.update(this.bidiCache, update.changes)
if (!update.empty) {
// 更新插件
this.updatePlugins(update)
// 更新输入状态
this.inputState.update(update)
}
// 更新文档视图
redrawn = this.docView.update(update)
// ...
if (!update.empty) {
for (let listener of this.state.facet(updateListener)) {
listener(update)
}
}
在这个过程中涉及到了前面所说的DocView的update过程、ViewState的测量过程,如此就和前面所说的东西衔接起来了。
预报:下周将开始阐述EditorState相关的东西,如果没有关注知之洲,可以关注一下本公众号。