官方推荐
其实 React 官方文档就提供了调试方法:
1. 创建项目
我们主要看如何对已有的 React 项目做调试,为了模拟这点,我们使用 create-react-app 先创建一个项目。
npx create-react-app react-app
2. 下载源码
现在我们下载 React 源码,存放在哪里都行,这里我存放在了和 react-app 同级目录(简单的说,都放桌面上了)
// 下载源码 git clone git@github.com:facebook/react.git // 进入源码目录 cd react // 安装依赖 yarn
3. 用源码构建文件
// 构建文件 yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE // link react cd build/node_modules/react yarn link // link react-dom cd ../react-dom yarn link
注意构建需要安装 JDK,如果构建的时候报了这样的错误:
就说明 JDK 还没有安装,点击跳转安装后,再次尝试构建。
4. link 文件
// 进入 react-app 项目目录后 yarn link react react-dom // 启动项目 yarn start
启动后查看源代码,我们会发现 react、react-dom 已经 link 到我们构建的 react 文件上
5. 调试
现在我们可以直接修改 react 项目构建出的 react-dom.development.js
文件,在其中添加调试代码,页面会自动刷新。
6. 问题
这样虽然也很好用,但是有一个问题,那就是我们直接调试的是编译后的 react-dom.development.js
,它并不是源码目录的文件,而是构建出来的打包文件。
如果我们修改了源码文件,那么我们就还要再次执行编译,打包成新的 react-dom.development.js
文件,这就比较麻烦了。
而且我们看源码看的肯定是 React 项目源文件,而不是看 react-dom.development.js
文件,如果我想直接调试 React 项目源文件,然后可以立刻生效,怎么办呢?
那么我们可以尝试下面这种方法。
webpack alias
这种方法原理很简单,利用 webpack 的 alias 将 react、react-dom 等库指向到源码文件,但是直接使用源码,运行的时候肯定会有很多问题,到时候我们见招拆招。
1. 创建 React 项目
npx create-react-app react-app cd react-app
2. 在 React 项目里下载 React 源码
考虑到 React 的开发者们总是不断地提交代码,如果直接拉取,可能会导致乱七八糟的问题,所以我们使用已发布的稳定版本,这篇文章在发布的时候, npm 最新的版本为 18.2.0,所以我们这里是以 18.2.0 版本的代码为例:
// 这次我们将存放目录放在项目文件里的 src 目录下 cd src // 我们下载的是带有 v18.2.0 tag 的版本 git clone --branch v18.2.0 git@github.com:facebook/react.git
3. 开启自定义配置
create-react-app 提供了自定义配置的方式,那就是使用 npm run eject
,因为这是个不可恢复的操作,使用前,注意把代码先提交了,当然你不提交,create-react-app 也会提示你进行提交。
npm run eject
执行成功后,我们再看下 package.json 文件,会发生很多变化,注意在 scripts 这里,以前是:
{ "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" } }
现在变成了:
"scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" },
我们查看 scripts/start.js
文件,通过查看其中的代码,可以发现 webpack 的配置文件放在了 config/webpack.config.js
下
4. 修改 webpack alias
我们打开 config/webpack.config.js
文件,搜索 alias
,修改如下:
// 修改之前 alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', // Allows for better profiling with ReactDevTools ...(isEnvProductionProfile && { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling', }), ...(modules.webpackAliases || {}), }, // 修改之后 alias: { react: path.join(paths.appSrc, 'react/packages/react'), 'react-dom': path.join(paths.appSrc, 'react/packages/react-dom') }
现在我们执行 npm start
启动下项目,你会发现有 142 个编译错误……
但不用害怕,我们慢慢解决。
有一类报错是这样的:
这类问题都可以通过 alias 解决,最终 alias 的配置修改如下:
// 修改之后 alias: { react: path.join(paths.appSrc, 'react/packages/react'), 'react-dom': path.join(paths.appSrc, 'react/packages/react-dom'), shared: path.join(paths.appSrc, 'react/packages/shared'), 'react-reconciler': path.join(paths.appSrc, 'react/packages/react-reconciler') }
再次执行 npm start,你会发现只有 5 个报错了:
5. 错误 1:修改 React 和 ReactDOM 引入方式
在这剩余的 5 个报错中,有一个报错是:
为了避免这个错误,我们打开 react-app/src/index.js
,修改 React 和 ReactDOM 的引入方式:
// react-app/src/index.js // 修改前 import React from 'react'; import ReactDOM from 'react-dom/client'; // 修改后 import * as React from 'react'; import * as ReactDOM from 'react-dom/client';
6. 错误2:修改 Scheduler
有两个报错如下:
按照指示,我们打开 react-app/src/react/packages/react-reconciler/src/Scheduler.js
文件:
// this doesn't actually exist on the scheduler, but it *does* // on scheduler/unstable_mock, which we'll need for internal testing export const unstable_yieldValue = Scheduler.unstable_yieldValue; export const unstable_setDisableYieldValue = Scheduler.unstable_setDisableYieldValue;
经过搜索,这两个常量的定义是在 react-app/src/react/packages/scheduler/src/forks/SchedulerMock.js
下,我们引入并修改一下:
// react-app/src/react/packages/react-reconciler/src/Scheduler.js // 在开头引入 SchedulerMock import * as SchedulerMock from 'scheduler/src/forks/SchedulerMock'; // 修改前 export const unstable_yieldValue = Scheduler.unstable_yieldValue; export const unstable_setDisableYieldValue = Scheduler.unstable_setDisableYieldValue; // 修改后 export const unstable_yieldValue = SchedulerMock.unstable_yieldValue; export const unstable_setDisableYieldValue = SchedulerMock.unstable_setDisableYieldValue;
7. 错误 3:关掉 ESlint
还有一个报错是:
为了简单起见,我们直接关掉 ESlint,打开 react-app/config/webpack.config.js
文件,搜索 disableESLintPlugin
:
// react-app/config/webpack.config.js // 修改前 const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true'; // 修改后 const disableESLintPlugin = true;
8. 错误 4:环境变量
到这里应该就没有编译错了,但是重新运行后,页面空白,打开控制台,我们会看到报错:
React 的源码里有直接使用 __DEV__
等环境变量,我们直接替换掉,修改 config/env.js
:
// react-app/config/env.js // 修改前 const stringified = { 'process.env': Object.keys(raw).reduce((env, key) => { env[key] = JSON.stringify(raw[key]); return env; }, {}) }; // 修改后(只修改 __DEV__ 还会有其他的相同报错,所以我们直接一次改齐) const stringified = { 'process.env': Object.keys(raw).reduce((env, key) => { env[key] = JSON.stringify(raw[key]); return env; }, {}), __DEV__: true, __EXPERIMENTAL__: true, __PROFILE__: true };
9. 错误 5:__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
还有报错:
首先找到报错文件:react-app/src/react/packages/shared/ReactSharedInternals.js
:
import * as React from 'react'; const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; export default ReactSharedInternals;
通过全局搜索 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,我们最终找到 ReactSharedInternals 的定义在 react-app/src/react/packages/react/src/ReactSharedInternals.js
,我们修改如下:
// react-app/src/react/packages/shared/ReactSharedInternals.js // 修改前 const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; // 修改后 import ReactSharedInternals from 'react/src/ReactSharedInternals'
10. 错误 6:This module must be shimmed by a specific renderer
还有报错:
打开 src/react/packages/react-reconciler/src/ReactFiberHostConfig.js
// We expect that our Rollup, Jest, and Flow configurations // always shim this module with the corresponding host config // (either provided by a renderer, or a generic shim for npm). // // We should never resolve to this file, but it exists to make // sure that if we *do* accidentally break the configuration, // the failure isn't silent. throw new Error('This module must be shimmed by a specific renderer.');
可以看到这个文件是会在编译的时候被替换成对应的 host config,我们直接修改如下:
// src/react/packages/react-reconciler/src/ReactFiberHostConfig.js // 修改前 throw new Error('This module must be shimmed by a specific renderer.'); // 修改后 export * from "./forks/ReactFiberHostConfig.dom";
11. 调试
到这里,代码应该已经可以正常运行起来了。
我们可以直接修改 react 的源码文件进行调试,浏览器会自动刷新,比如我修改了react/packages/scheduler/src/SchedulerMinHeap.js
等文件:
export function push(heap: Heap, node: Node): void { const index = heap.length; heap.push(node); console.log(JSON.stringify(heap)) siftUp(heap, node, index); }
可以看到浏览器打印了: