slate-react 事件管线与输入法兼容
1. 先给结论
Editable 的核心职责不是渲染,而是“把不稳定的浏览器输入事件,转成稳定的 Slate 操作序列”。
尤其 IME(中文/日文/韩文)场景,Slate 做了大量防抖、延迟、兜底与平台分流。
2. 事件主链路(简化版)
- 原生
beforeinput(不是 React polyfill) - 解析
inputType(insert/delete/composition/drop/paste) - 必要时先同步选区(
toSlateRange -> Transforms.select) - 决定 native 还是 Slate 接管(
event.preventDefault与否) - 调用
Editor.insertText/delete*等命令 - 结束后尝试恢复用户真实选区
源码依据:
packages/slate-react/src/components/editable.tsx- 明确注释:React 的
beforeinput是“leaky polyfill”,所以要绑定原生事件
源码注释(节选):
ts
// Listen on the native `beforeinput` event to get real "Level 2" events.
// This is required because React's `beforeinput` is fake...
node.addEventListener('beforeinput', onDOMBeforeInput)3. 为什么 IME 难
IME 期间常见特性:
beforeinput不一定可取消(Android 更明显)- composition 事件顺序在不同浏览器不一致
- DOM 可能先被浏览器改,再轮到框架状态更新
Slate 的应对策略:
- 维护
isComposing状态 - composition change 类型先放行,不急于干预
- Android 走专门 input manager 分支
源码注释(节选):
ts
// COMPAT: BeforeInput events aren't cancelable on android,
// so we have to handle them differently using the android input manager.
if (androidInputManagerRef.current) {
return androidInputManagerRef.current.handleDOMBeforeInput(event)
}相关实现:
use-composing.tshooks/android-input-manager/*editable.tsx中onCompositionStart/Update/End+onDOMBeforeInput
4. Android 专用输入管理器
文件:packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts
这是一个非常工程化的补丁层:
- 监听 MutationObserver
- 结合 selection 状态决定何时 flush
- 对部分 inputType 做特判
- 避免“看起来输入成功,但 Slate 状态和 DOM 不一致”
对应 issue:
5. 兼容策略中的现实权衡
Editable 中经常出现:
- “某浏览器可用 native 插入更快,但某场景会重复插入”
- “理论上应全部 preventDefault,但某些输入法不可取消”
- “应该马上同步选区,但过早会导致跳光标”
所以 Slate 采用了条件式 native fallback 与延迟回补并存的策略。
这也是为什么富文本输入层很难“优雅重写”。
6. 与 React 状态同步
packages/slate-react/src/components/slate.tsx 中:
- 初始化时写入
editor.children = initialValue onContextChange里根据 operation 类型区分触发:onSelectionChangeonValueChangeonChange
这保证了“选择变化”和“内容变化”可分流订阅。
7. 答法要点(聚焦工程认知)
Slate React 层本质是输入事件编排器。
对 IME 的处理不是单个 bugfix,而是完整策略:原生 beforeinput、composition 状态机、Android 专用 manager、选择恢复机制。
这类代码的难点在于跨浏览器行为一致性,不是 API 设计本身。