news 2026/9/13 15:27:27

如何用 react-scan/lite 无头插桩获取 commit 与 render 事件流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何用 react-scan/lite 无头插桩获取 commit 与 render 事件流

如何用 react-scan/lite 无头插桩获取 commit 与 render 事件流

【免费下载链接】react-scanScan and fix React performance issues项目地址: https://gitcode.com/GitHub_Trending/re/react-scan

如果你需要在不引入 react-scan 工具栏、不渲染任何 UI 的前提下,以编程方式拿到 React 应用的 commit 与 render 事件流——比如把每个组件的重渲染原因记录到日志,或把事件 POST 到一个 ingest 端点——react-scan/lite就是这个用途。它是一个极小的无 UI 入口,接入的是 React DevTools 的 profiling 通道(Timeline Profiler 使用的同一通道),产出的是 commit / render / state-update 等事件流,可以通过回调消费,也可以通过fetch上报到端点。

准备条件:安装与版本要求

安装 react-scan 主包,react-scan/lite是它的子路径导出,无需单独安装:

npm i react-scan

或使用 pnpm / yarn:

pnpm add react-scan
yarn add react-scan

包的 peer 依赖要求 React / react-dom 在^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0范围内。package.jsonexports中声明了./lite子路径,分别映射到dist/lite/index.mjsdist/lite/index.js

插入时机:必须在 react-dom 挂载之前

instrument()必须运行在react-dom挂载之前:放在入口文件最顶部,或作为<head>中的第一个 script。原因与 react-scan 主入口一致——它需要在 React 拿到__REACT_DEVTOOLS_GLOBAL_HOOK__之前接管这个 hook(参考 docs/installation/vite.md 中的说明)。

instrument()是 SSR 安全的:在 Node 环境中调用会返回一个 noop handle,其isActive()返回false,所有方法可安全调用而不抛错。

最小插入示例

下面的主路径只接回调,把事件打印到控制台(示例来自 packages/scan/README.md 的react-scan/lite章节):

import { instrument } from "react-scan/lite"; const handle = instrument({ onEvent: (event) => console.log(event), }); // 结束时调用,幂等 handle.stop();

instrument()返回LiteHandle,包含三个成员:

  • subscribe(listener):追加一个监听器,接收所有已发出的事件,返回一个取消订阅函数。onEventsubscribe可以组合使用,回调先于订阅者触发;
  • isActive():当前是否仍在发出事件;
  • stop():停止发事件,幂等。调用后 hook 上被替换的 handler 会被还原为原始实现。

事件对象LiteEvent的公共字段是kindtimestamp,按需附带componentNamelaneslaneLabelspriorityLevelpriorityNametreedidError等字段,完整定义见 packages/scan/src/lite/types.ts。

理解事件流

LiteEventKind覆盖以下类别(节选自 types.ts):

  • 通道状态:renderer-injectedprofiling-hooks-status
  • commit 周期:commitpost-commitfiber-unmountcommit-start/commit-stoprender-start/render-yield/render-stop/render-scheduled
  • effect 边界:layout-effects-start/stoppassive-effects-start/stop
  • 组件级:component-render-start/stop,各 layout / passive effect 的 mount/unmount 事件,state-updateforce-updatecomponent-suspendedcomponent-errored

其中两类字段值得注意:

  1. commit事件:每次 commit 携带tree字段,是按 fiber 展开的LiteFiberSummary数组,每个条目含namedepthtagactualDurationactualStartTimeselfBaseDurationtreeBaseDuration,以及启用对应选项后出现的fiberIdsource/ownerNamechangeDescription。事件还带priorityName(如"UserBlocking"),由 scheduler 优先级经getLaneLabelMap翻译得到;commit 中发生错误时会带didError: true
  2. profiling-hooks-status事件injectProfilingHooks成功时为available: true;失败时available: false,并带reasonno-inject-method/threw/opted-out)、reactVersionbundleType。这是判断mark*类事件是否会触发的依据。

可选:为每个 fiber 附加重渲染原因

recordChangeDescriptions(默认false)开启后,每个 fiber 摘要附带changeDescription: { isFirstMount, props, state, context, hooks, parent },直接回答"这个组件为什么重渲染了"。各 per-fiber 选项及默认值(README 与 types.ts 一致):

选项默认值效果
recordChangeDescriptionsfalse每个 fiber 附带changeDescription,归因到 props / state / context / hooks / parent
includeFiberSourcefalse附带source: { fileName, lineNumber, columnNumber }ownerName,来自_debugSource(React 16/17/18)或_debugStack(React 19+),同步提取,无 source-map 解析
includeFiberIdentityfalse附带稳定的单调递增fiberId(alternate-aware),可用于跨 commit 关联同一逻辑 fiber
includeLaneLabelstrue翻译laneLabelspriorityName
maxFibersPerCommit5000单个 commit 最多遍历的 fiber 数,超出截断以限制 payload
minFiberActualDurationMs0跳过actualDuration低于该阈值的 fiber 子树

注意:把includeFiberTree设为false会整体关闭 fiber 树遍历,此时三个 enrichment 选项全部失效,instrument()会打印一次性警告提示这一点。

可选:把事件流 POST 到 ingest 端点

instrument()endpoint后,每个事件会额外以fetchPOST 上报,请求体为{ sessionId, location, message, data, timestamp }keepalive: trueContent-Type: application/json,实现见 create-emitter.ts):

const handle = instrument({ onEvent: (event) => console.log(event), // 可选:把每个事件上报到端点 endpoint: "http://127.0.0.1:54321/ingest/abc123", // 替换为你的接收地址 sessionId: "abc123", // 你的会话标识 });

使用端点上报时的硬性规则:

  • endpoint必须同时提供sessionId,否则不发 POST,并打印一次警告:`[react-scan/lite] `endpoint` requires `sessionId`; events will not be POSTed.`
  • endpoint必须在instrument()时刻就是合法的 http/https URL,否则该 endpoint 被直接丢弃(后续事件不会逐条去 fetch 失败),并打印错误`[react-scan/lite] `endpoint` is not a valid http(s) URL; events will not be POSTed.`
  • keepalive请求体有约 64KB 上限,带大tree的 payload 可能被静默丢弃;
  • location选项(默认"ReactScanLite")作为每次 POST 中location字段的前缀。

构建档位限制:actualDuration只在__PROFILE__构建中填充

actualDurationmark*profiling hooks 只在__PROFILE__构建中有数据。开发环境默认满足;生产环境需要在打包器里把react-domalias 到react-dom/profiling(webpack/vite 的resolve.alias)。否则onCommitFiberRoot仍会触发commit事件,但每个 fiber 的actualDuration都是0

此时profiling-hooks-status事件会给出明确信号:available: falsereason: "no-inject-method"表示 React 19.2+ 的生产构建或非__PROFILE__构建,mark*hooks 不会触发。README 建议的消费方式是:在这种情况下回退到long-animation-frame(LoAF)归因,而不是判定为"应用空闲"。

与 React DevTools 共存的行为

如果 React DevTools 同时已 attach,instrument()会打印一次性警告:injectProfilingHooks通道是硬性替换,插桩激活期间 DevTools 的 Timeline Profiler 收不到事件。调用handle.stop()会还原 hook 上的原始 handler(injectonCommitFiberRootonPostCommitFiberRootonCommitFiberUnmount),DevTools 恢复接收。

同一页面内重复调用instrument()(未stop())会直接返回已有 handle,不会重复挂接。

验证插桩是否生效

按 lite.test.ts 中验证过的事件形态,在你的开发环境下可以这样核对:

  1. 调用instrument()后立即检查handle.isActive()true(浏览器环境;Node 环境下为false,属于 SSR noop 行为);
  2. profiling-hooks-status事件中确认available: true。开发构建下这是预期结果;若为available: false,按上面的reason值区分是生产/非__PROFILE__构建(no-inject-method)、注入抛错(threw,此时事件带error信息)还是主动关闭(opted-out);
  3. 触发一次状态更新后,确认收到kind === "commit"的事件,且tree数组包含组件名、depthactualDuration等字段;开启recordChangeDescriptions时可进一步检查某条目的changeDescription(首次挂载时isFirstMounttrue);
  4. 调用handle.stop()后再触发更新,确认不再收到 commit 事件,且isActive()false;重复调用stop()不抛错。

subscribe返回的取消订阅函数只移除单个监听器,不影响其他监听者与 endpoint POST;彻底停止请用handle.stop()

边界与下一步

  • mark*类事件(组件 render、effect 边界、state-update等)只在 React 16.5–19.1 的__PROFILE__构建中触发;
  • changeDescriptionhooks字段是近似值:按引用比较每个 hook 的memoizedStateuseMemo/useCallback的变化也会被报告;host 节点等类型的 fiber 其changeDescriptionnull
  • includeFiberSource只做同步提取,没有 source-map symbolication,React 19+ 从_debugStack解析,fiber 缺少 debug 信息时sourceownerNamenull

事件流的完整类型定义(LiteEventLiteOptionsLiteHandleProfilingHooks)都在 packages/scan/src/lite/types.ts,插桩与 hook 替换逻辑在 packages/scan/src/lite/index.ts,fiber 遍历在 packages/scan/src/lite/walk-fiber.ts,可继续深入。

【免费下载链接】react-scanScan and fix React performance issues项目地址: https://gitcode.com/GitHub_Trending/re/react-scan

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 15:27:21

Vulkan图形渲染管线构建与性能优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:26:56

华为OD机考双机位C卷流量波峰Java解题指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:25:18

多模态Vision API调用实战:图片理解、参数调优与成本控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:25:09

2026年5G随身WiFi与CPE深度解析:槽点、套路与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 15:23:20

8款高性价比AI写作辅助网站横向实测,本硕博撰稿避坑全指南

前言&#xff1a;AI 写论文乱象频发&#xff0c;实测 8 款工具理清适配边界 每到毕业季&#xff0c;本科生、硕博生都会集中寻找 AI 论文辅助工具&#xff0c;市面各类写作软件层出不穷&#xff0c;但普遍存在几类硬伤&#xff1a;虚假参考文献、无法匹配本校格式、不支持公式代…

作者头像 李华
网站建设 2026/9/13 15:23:02

大宽表实战指南:从业务路径出发构建高性能分析底座

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华