React Scan 在 Next.js Page Router 中的接入指南:script 标签与模块导入两种方式详解
【免费下载链接】react-scanScan and fix React performance issues项目地址: https://gitcode.com/GitHub_Trending/re/react-scan
本篇技术指南以 React Scan 官方文档中的 Next.js Page Router 安装指南 为主体,详细讲解如何在基于Pages Router(pages/目录)的 Next.js 应用中接入 React Scan,以扫描并定位 React 组件的重复渲染与性能瓶颈。读完本文,你将掌握两种接入方式(pages/_document中的 CDN script 标签、pages/_app中的模块导入)、如何在生产环境强制启用扫描(react-scan/all-environments),并能理解其"仅开发环境生效"的底层判定逻辑,从而避免踩坑。
适用前提
React Scan(react-scan包,当前仓库版本为0.5.7,见 packages/scan/package.json)通过 React DevTools Hook(基于bippy)在运行时自动检测 React 渲染行为,无需改动业务组件代码。其 peerDependencies 声明支持react/react-dom的^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0,因此 Pages Router 下无论是 Next.js 12、13、14 还是 15 的项目,只要满足上述 React 版本要求即可接入。
本文针对的是Page Router(pages/目录);如果你的项目使用 App Router(app/目录),请参考 App Router 安装指南。
方式一:通过 CDN script 标签接入
这是最快的接入方式——不需要安装任何 npm 包,只需要在pages/_document中添加一个 script 标签。
修改pages/_document
打开(或创建)pages/_document,将 React Scan 的auto.global.js以<script>标签引入到<Head>中:
// pages/_document import { Html, Head, Main, NextScript } from "next/document"; export default function Document() { return ( <Html lang="en"> <Head> <script src="https://unpkg.com/react-scan/dist/auto.global.js" /> {/* 其余脚本放在下面 */} </Head> <body> <Main /> <NextScript /> </body> </Html> ); }之所以放在pages/_document的<Head>里,是因为_document只在服务端渲染时执行,最终生成的auto.global.js会随首屏 HTML 一并下发,从而保证脚本在应用 JavaScript 执行前就绪。
可用的 CDN 地址
官方 CDN 指南 提供以下两个等价地址,任选其一:
<!-- JSDelivr --> <script src="https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js"></script> <!-- UNPKG --> <script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>auto.global.js 做了什么
auto.global.js对应源码 packages/scan/src/auto.ts:
import './polyfills'; // Prioritize bippy side-effect import 'bippy'; import { IS_CLIENT } from '~web/utils/constants'; import { scan } from './index'; if (IS_CLIENT) { scan(); window.reactScan = scan; }即在客户端环境下自动调用scan()完成初始化,无需任何手动配置;同时把scan挂到window.reactScan上,方便在浏览器控制台里随时手动开关扫描。这正是 script 标签方式"开箱即用"的原因。
方式二:通过模块导入接入
如果你更希望把依赖纳入 npm 管理(而非 CDN),可以采用模块导入方式。首先安装:
npm install react-scan # 或 pnpm add react-scan然后在pages/_app中完成初始化。
修改pages/_app
// pages/_app // react-scan 必须是文件中最顶部的导入 import { scan } from "react-scan"; import { useEffect } from "react"; export default function App({ Component, pageProps }) { useEffect(() => { // 确保在 React 水合(hydration)完成之后再启动扫描 scan({ enabled: true, }); }, []); return <Component {...pageProps} />; }这里有两点必须注意:
import { scan } from "react-scan"必须是文件中最顶部的导入。React Scan 依赖bippy的副作用来安装 React 内部的 hook,只有先于 React 相关模块执行,才能在 React 渲染管线中挂上探针。若导入顺序不对,控制台会在 5 秒后报出[React Scan] Failed to load. Must import React Scan before React runs.(该逻辑见 packages/scan/src/core/index.ts)。scan()必须放在useEffect中、即水合完成之后调用。在渲染阶段调用会干扰 React 的正常提交流程;放在useEffect空依赖数组中可保证只在客户端、只在首次挂载后执行一次。
在生产环境也启用扫描:react-scan/all-environments
默认情况下,React Scan只在开发环境运行——这是由 packages/scan/src/core/index.ts 中start()的逻辑决定的:当检测到当前 React 为生产构建(getIsProduction()返回true)且未开启dangerouslyForceRunInProduction时,直接跳过初始化。
如果你需要在生产环境(例如线上复现性能问题)也启用扫描,把导入路径换成react-scan/all-environments:
- import { scan } from "react-scan"; + import { scan } from "react-scan/all-environments";该导出子路径在 packages/scan/package.json 中声明,其实现位于 packages/scan/src/core/all-environments.ts:
import { ReactScanInternals, scan as innerScan } from '.'; export const scan = /*#__PURE__*/ (...params: Parameters<typeof innerScan>) => { if (typeof window !== 'undefined') { ReactScanInternals.runInAllEnvironments = true; innerScan(...params); } };原理很简单:调用前先把内部标志ReactScanInternals.runInAllEnvironments置为true,start()中的环境检查(见 packages/scan/src/core/index.ts)便会跳过生产环境拦截,从而实现任意环境(开发、生产、iframe)都能扫描。script 标签方式没有等价的"生产开关",如需生产扫描请使用模块导入方式。
源码级原理解析:scan() 的完整调用链
模块导入方式触发的核心调用链为scan(options)→setOptions(options)+start(),均位于 packages/scan/src/core/index.ts:
export const scan = (options: Options = {}) => { setOptions(options); const isInIframe = Store.isInIframe.value; if ( isInIframe && !ReactScanInternals.options.value.allowInIframe && !ReactScanInternals.runInAllEnvironments ) { return; } if (options.enabled === false && options.showToolbar !== true) { return; } start(); };可以看到三个关键判定:
- iframe 保护:默认
allowInIframe为false,在 iframe 内默认不启动(除非显式开启或使用all-environments); - 显式关闭:
enabled: false且未要求显示工具栏时直接返回; - 环境门槛:
start()内通过getIsProduction()判断当前 React 是否为生产构建(见 packages/scan/src/core/index.ts)。
值得注意的一个细节:getIsProduction()对"检测到开发构建"的结果做了永久缓存,但故意不缓存"生产构建"的结果。其测试用例 packages/scan/src/core/get-is-production.test.ts 注释了原因——Next.js 的 dev overlay 会先注册一个生产版 React 渲染器,随后用户的开发版 React 才在下一 tick 注册;若把首次的true缓存下来,就会把 dev 环境误判为生产而锁死扫描。这个边界情况正是 Next.js 项目中最容易踩到的坑,也是官方通过回归测试专门防护的。
常用配置项
在pages/_app中调用scan()时传入的Options定义于 packages/scan/src/core/index.ts,常用项及默认值如下:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 是否启用扫描,推荐写成process.env.NODE_ENV === 'development' |
showToolbar | boolean | true | 是否显示右下角工具栏;设为true时即使enabled: false工具栏仍显示,但扫描关闭 |
animationSpeed | "slow" \| "fast" \| "off" | "fast" | 渲染高亮动画速度 |
log | boolean | false | 将渲染日志打印到控制台,注意频繁渲染时会带来明显开销 |
trackUnnecessaryRenders | boolean | false | 追踪"无效渲染"(组件重渲染但 DOM 子树无变化)并以灰色轮廓标记,会带来额外开销 |
showFPS | boolean | true | 工具栏中是否显示 FPS 计数器 |
showNotificationCount | boolean | true | 工具栏中是否显示性能提醒数量 |
allowInIframe | boolean | false | 是否允许在 iframe 内运行 |
safeArea | number \| { top?, right?, bottom?, left? } | 24 | 工具栏距视口边缘的像素距离,可避免与 Next.js dev indicator 等覆盖层重叠 |
dangerouslyForceRunInProduction | boolean | false | 强制在生产环境运行(不推荐,优先使用all-environments导入) |
一个推荐的开发环境写法:
import { scan } from "react-scan"; import { useEffect } from "react"; export default function App({ Component, pageProps }) { useEffect(() => { scan({ enabled: process.env.NODE_ENV === "development", trackUnnecessaryRenders: true, }); }, []); return <Component {...pageProps} />; }注意:setOptions会通过validateOptions校验传入项(见 packages/scan/src/core/index.ts),非法值(如animationSpeed传了"normal")不会生效,并会在控制台输出[React Scan] Invalid options警告;enabled等布尔选项同时会被持久化到localStorage(键名react-scan-options),因此刷新页面后开关状态会保留。
常见问题排查
- 控制台出现
[React Scan] Failed to load. Must import React Scan before React runs.:说明react-scan的导入不在最顶部,或 script 标签加载晚于 React 执行。请调整导入顺序,或改用pages/_document的 CDN 方式。 - 生产环境没有高亮效果:这是预期行为——默认仅开发环境生效。需要生产扫描请改用
react-scan/all-environments。 - 页面在 iframe 中无扫描效果:默认
allowInIframe: false,若目标场景在 iframe 中,请显式开启该选项。 - 工具栏与 Next.js 自带开发指示器重叠:使用
safeArea配置项调整工具栏的安全间距。
小结
在 Next.js Page Router 项目中接入 React Scan 共有两条路径:CDN script 标签(零依赖、修改pages/_document即可)与模块导入(纳入 npm 管理、修改pages/_app)。两者都遵循同一底层实现——通过bippy安装 React hook、scan()完成初始化的调用链,并默认只在开发环境生效;如需生产环境扫描,模块导入方式可无缝切换到react-scan/all-environments。接入之后,组件每次渲染都会以高亮轮廓的形式呈现在页面上,配合工具栏的 FPS 与渲染统计,即可快速定位"谁在渲染、为什么渲染"。
【免费下载链接】react-scanScan and fix React performance issues项目地址: https://gitcode.com/GitHub_Trending/re/react-scan
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考