Skip to content

Conversation

@cyfung1031
Copy link
Collaborator

@cyfung1031 cyfung1031 commented Feb 8, 2026

React 16.14.0 -> React 17.0.0

经测试,会在 dom 加入这些事件

ArcoDesign 实际上只支持到 React 16.x (16.13.0)

所以没针对React >=17 作出优化?

( ArcoDesign 以 onFocus onBlur 作出发点,但实际上是用 focusin focusout)
( ScriptCat的设计,一个页面会有多个 tabIndex 项,所以 click 的时候会有这样的 focusin focusout ?)
(React 本身好像只會 anchor 在 div#root, 但 ArcoDesign 好像因為有Modal之類的會 anchor 在 body? 所以兩者互相衝突了? )

https://github.com/arco-design/arco-design/blob/72a881a37a5957d16021cf26d39e6e2e63b05021/package.json#L120-L121


我不知道是ArcoDesign本身的问题还是什么了
我估计它在初始化时会加一些 event 在 body 和 root
但它加了之后,在Re-render 时又再加一次
所以会有重复触发

在 ScriptList Table
有300个scripts
打勾一下
你会发现拖超慢
然后你看一下 DevTools, 会显示为 focusin focusout 超时

打印一下的话,会发现 focusin focusout 的 handler 一次打勾会执行 8次左右
现在加了这个 fix, 打勾要等一秒的情况就完全消失了 (打勾不会卡死在 focusin focusout)


整个体验快乐得多了 ...

screen-capture.25.online-video-cutter.com.-00.00.00.000-00.00.28.559.mp4

@cyfung1031 cyfung1031 changed the title [v1.3] 修正 ArcoDesign/React 的 focusin/out 事件问题 [v1.3] 显著提升 UI 响应速度 —— 修复 ArcoDesign/React 中 focusin / focusout 事件问题 Feb 8, 2026
@CodFrm
Copy link
Member

CodFrm commented Feb 8, 2026

如果是arco的问题,我觉得可以给arco提pr,让他们发新包更新一下,不过鉴于arco维护并不积极,可以临时这样处理一下

看代码感觉侵入性还是太强了

@CodFrm
Copy link
Member

CodFrm commented Feb 8, 2026

如果是arco的问题,我觉得可以给arco提pr,让他们发新包更新一下,不过鉴于arco维护并不积极,可以这样处理一下

Co-authored-by: wangyizhi <i@xloli.top>
@cyfung1031
Copy link
Collaborator Author

如果是arco的问题,我觉得可以给arco提pr,让他们发新包更新一下,不过鉴于arco维护并不积极,可以这样处理一下

老实说我也不知道是 React 问题 还是 ArcoDesign 问题 还是 ScriptCat 业务代码问题
提PR或提issue 都不知道在哪提,怎样提,提什么。
就这样处理算了。

@cyfung1031
Copy link
Collaborator Author

加了注释。

@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Feb 9, 2026

这段程式码的目的是为了解决一个特定的效能瓶颈问题,这个问题发生在同时使用 React 17(或更高版本)Arco Design(一个 UI 元件库)时。

以下是关于为什么需要这段程式码的详细解释:

核心原因:事件机制的冲突与效能问题

1. 背景:React 17 的事件委派变更
在 React 17 之前,React 会将大部分事件委派(delegate)到 document 级别。但从 React 17 开始,为了更好地支援微前端和多版本 React 共存,React 将事件委派改到了 React 应用的根节点(例如 <div id="root"></div>)。

2. 问题点:Arco Design 的 focusin / focusout 监听
Arco Design 的某些组件(可能是为了处理全局焦点管理、弹窗关闭等逻辑)会在 document.body 或 React 根节点上注册 focusinfocusout 事件监听器。

3. 冲突产生的结果:重复触发与 UI 卡顿
在 React 17+ 的环境下,由于事件冒泡机制和 Arco 内部实现的某种交互,导致在同一个渲染帧(rendering frame)内,当用户进行聚焦操作时,focusinfocusout 事件会被 Arco 的逻辑异常地、重复地多次触发。

浏览器需要在极短的时间内处理这些大量的冗余事件回调,这会占用大量的 CPU 资源,导致主执行绪阻塞,用户感知到的就是页面操作卡顿(UI Freeze)


解决方案的运作原理 (How it works)

这段程式码的核心思想是**「防抖(Debounce)」「延迟执行」**。它不再让这些事件立刻执行,而是把它们「收集」起来,等到浏览器稍空闲的下一个时刻统一执行一次。

具体步骤如下:

  1. 拦截 (Intercept):
    程式码透过「Monkey Patch」的方式,修改了 document.body 和 React 根节点 (#root) 的 addEventListener 方法。
  2. 过滤 (Filter):
    新的 addEventListener 会检查传入的事件类型。它只拦截 focusinfocusout,且只针对最简单的监听配置(排除 oncepassive 等复杂选项,以免破坏标准行为)。其他的事件监听则照常放行。
  3. 缓存与延迟 (Buffer & Delay):
    当拦截到目标事件时,它不会立刻执行原始的 listener
  • 它会将这次事件资讯(事件物件、对应的 this 上下文、原始回调函数)存入一个集合 (stackedEventsbindInfoMap) 中。
  • 然后,它利用 self.postMessage 发送一个讯号。这是一种在浏览器中创建「Macrotask(宏任务)」的高效技巧(通常比 setTimeout(0) 更快、开销更小)。这个讯号的目的是告诉浏览器:「在处理完当前所有同步任务后,尽快执行我的处理函数」。
  1. 执行 (Execute):
    当浏览器进入下一个 Macrotask 事件循环时,会接收到 postMessage 的讯号并执行 executorFn
  • executorFn 会取出之前缓存的所有事件。
  • 它会遍历这些事件,并呼叫原始的监听器函数。
  • 关键点: 因为这个执行过程发生在下一个事件循环,此时之前的「同一帧内重复触发」风暴已经结束。透过这种方式,原本可能在一瞬间触发几十次的昂贵操作,被合并并延迟到下一个 tick 里有序地执行,从而避免了 UI 卡顿。

总结:
这段程式码是一个针对特定技术栈组合(React 17+ & Arco Design)产生的效能缺陷的「补丁(Polyfill/Hack)」。它透过劫持原生的事件监听方法,将高频触发的焦点事件放入缓冲区,并延迟到下一个事件循环中执行,以此来平滑效能峰值,解决卡顿问题。


Gemini_Generated_Image_3k65st3k65st3k65

@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Feb 9, 2026

应该只有 focusin/focusout
AI解释如下


是否存在类似事件?

存在,但真正值得像 focusin / focusout 这样采用 macrotask 延迟处理的同类事件非常少。

大多数问题集中在同时具备「同步冒泡 + 高频触发 + React/框架额外包装一层」特性的事件上。

以下按实际踩坑概率从高到低排序,而非罗列所有事件。


一、与 focusin / focusout 同级别(最需关注)

1. focus / blur

⚠️ 与 focusin/focusout 行为不完全相同)

事件 原生冒泡特性
focus 不冒泡
blur 不冒泡

为何仍需关注?

  • React 通过捕获阶段 + 合成事件模拟冒泡行为
  • 主流 UI 库(Arco、Ant Design、MUI 等)常同时监听 focus / focusin
  • React 17+ 事件委托已下放到 root 容器,重复触发问题更容易被放大


原生 focus/blur 不冒泡,浏览器不会像 focusin/focusout 那样产生「逐层爆炸式触发」。

结论
一般无需采用当前 macrotask hack,除非明确发现某 UI 库对 focus 事件进行了多层代理。


二、存在「同步连环触发」的高频 UI 事件(有一定风险)

特点:一次用户操作 → 同步触发多个事件 → React/框架再次包装

2. mouseover / mouseout(次高风险)

  • 子元素切换时会频繁反复触发
  • 冒泡
  • 多数组件库内部会基于它们衍生:hover、tooltip、popover、menu 等

为何通常没 focusin 那么严重?

  • 浏览器和 React 对 mouse 事件优化多年
  • UI 库普遍采用 throttle / debounce / pointer-events 等防护措施

结论
理论上存在类似风险,但不推荐使用 macrotask 延迟,性价比低,副作用可能大于收益。

3. pointerover / pointerout(🟡 现代且相对干净)

  • pointer 事件统一了 mouse / touch / pen
  • 触发路径更可预测、可控
  • UI 库在此类事件上踩坑较少

结论:基本无需特别担心。


三、真正的高频事件,但不适合用 macrotask 方案

4. scroll / resize(🚫 不建议此方式)

  • 触发极高频
  • 同步执行
  • scroll 不冒泡

若强行放入 macrotask:

  • UI 出现明显迟滞
  • 布局抖动加剧

推荐正确处理方式

  • requestAnimationFrame
  • throttle / debounce
  • passive: true

四、键盘与输入相关事件(需视场景而定)

5. input / change / keydown / keyup

在中文输入法(IME)场景下会产生:
compositionstart → compositionupdate → compositionend

一次输入可能导致原生 + React 多轮触发

这些事件语义极其敏感(光标位置、撤销栈、输入法交互等),延迟到 macrotask 极易引入 bug。

结论
禁止全局 hack,只能在具体业务组件内做针对性、精细化处理。


五、为何 focusin / focusout 尤其“特殊”?

当前 macrotask 方案几乎是为它们量身定制,主要原因同时满足以下六点:

  1. 同步触发
  2. 支持冒泡
  3. 在 DOM 路径上反复触发
  4. React 17+ 合成事件机制会再完整走一遍
  5. 众多 UI 库习惯在 document / body 层绑定
  6. 延迟一个 macrotask 对用户无明显感知

真正同时满足这六点的其他事件极少


六、实战判断标准(Mental Checklist)

未来遇到类似性能问题时,可快速对照以下条件:

只有同时满足全部条件的事件,才值得考虑 macrotask 延迟方案:

  • 支持冒泡
  • 同步触发
  • 一次用户动作会多次触发
  • UI 库 + React 都会额外包装一层
  • 延迟到下一个 macrotask 不破坏语义和用户体验

目前几乎仅限于:

  • focusin
  • focusout

@cyfung1031 cyfung1031 added P0 🚑 需要紧急处理的内容 hotfix 需要尽快更新到扩展商店 labels Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hotfix 需要尽快更新到扩展商店 P0 🚑 需要紧急处理的内容

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants