Skip to content
目录

React 集成与性能优化

1. 核心思想:把 Editor 实例生命周期从组件渲染中剥离

@tiptap/react 的关键不是“渲染一个编辑器”,而是“稳定管理一个长生命周期 Editor 实例,再按需订阅状态变化”。

对应实现:

  • useEditor.tsEditorInstanceManager
  • useEditorState.tsEditorStateManager
  • EditorContent.tsx:DOM 宿主与 NodeView Portal 桥接

2. useEditor 的实例管理

2.1 为什么不用简单 useState(new Editor())

直接在组件里新建实例,会在依赖变化、StrictMode 双调用、卸载重挂时产生重复创建/销毁问题。
Tiptap 采用 EditorInstanceManager 来做:

  • options 比较更新(尽量 setOptions 而不是重建)
  • deps 变化时才重建实例
  • scheduleDestroy 延迟销毁,防止同一 tick 的“误杀”

2.2 useSyncExternalStore 的价值

useEditoruseEditorState 都使用 useSyncExternalStore 系列 API:

  • 与 React 并发模型兼容
  • 将“外部可变对象(Editor)”纳入可预测订阅模式
  • 降低无关 re-render

3. SSR 与 immediatelyRender

useEditor 内部有 SSR/Next 场景判断:

  • SSR 环境推荐 immediatelyRender: false,避免 hydration mismatch。
  • CSR 可设为 true 获得更直接实例语义。

证据:

可说:这不是“功能开关”,本质是客户端 DOM 可用性与 hydration 一致性约束

源码片段(packages/react/src/useEditor.ts):

ts
if (this.options.current.immediatelyRender === undefined) {
  if (isSSR || isNext) {
    throw new Error(
      'Tiptap Error: SSR has been detected, please set `immediatelyRender` explicitly to `false` ...',
    )
  }
  return this.createEditor()
}

4. 事务驱动渲染控制

默认情况下,历史行为会在每次 transaction 触发重渲染;v3 引入更细粒度控制:

  • shouldRerenderOnTransaction(兼容开关)
  • useEditorState(selector, equalityFn)(推荐)

这样可以让工具栏只订阅 isBold/isItalic 等所需状态,而不是全量跟着文档变动重渲染。

5. EditorContent 与 Portal NodeViews

EditorContent.tsx 做了两件事:

  1. 接管并迁移 ProseMirror 生成的 DOM 到 React 容器。
  2. 通过 contentComponent 维护 NodeView ReactRenderer 的 portal 映射。

这让 NodeView 的 React 组件仍可使用 React 生命周期,同时保持 ProseMirror 对 contentDOM 的控制。

6. BubbleMenu 回归案例:响应性 vs 稳定性

相关:

  • 问题:props 更新不生效(Issue #6963
  • 修复尝试:PR #7028
  • 后续回归讨论:PR #7115

经验点:

  • 把更多 props 放进 effect 依赖可提高“响应变化能力”。
  • 但可能导致插件反复卸载/重建,引发 selection 与事务抖动。
  • 最终要做“可更新字段”和“实例级配置字段”的边界划分。

这是前端框架桥接层非常典型的设计权衡题。

7. 延伸追问建议

如果被问“你会怎么做 React 优化”:

  1. useEditor 实例尽量稳定,避免每次 render new Editor。
  2. 使用 useEditorState + selector 控制订阅粒度。
  3. 避免把未 memo 的复杂对象直接传给菜单类组件。
  4. SSR 场景明确设置 immediatelyRender,不要依赖隐式判断。

MIT License.