CodeMirror源码之回头看看(重要)

经过前面几个章节的挖掘,已经掌握了一些信息,此篇是要回头看一下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相关的东西,如果没有关注知之洲,可以关注一下本公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知之洲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值