我们使用 create-react-app 脚手架来调试 React 源码。
首先创建一个项目:
$ npx create-react-app debug-react
我们需要修改很多 webpack 相关的配置,这里把配置弹出来,修改起来更方便一些。
$ yarn eject
建议从官方仓库 facebook/react fork 一份到自己的名下,这样修改起来还方便一些。如我自己 fork 出来的仓库地址:wenzi0github/react。
在 src 目录中引入 react 源码,大概结构如下:
src
react # react源码
App.js
index.js
进入到 react 源码的目录,安装 react 所需要的 npm 包:
$ npm i
# or
$ yarn install
我这里把 debug-react 和 fork 出来的 react 源码放到了两个 Git 仓库中,因此需要在在 debug-react 项目的.gitignore
文件中,将 src/react 添加到忽略目录中。若您希望都放在一个 Git 仓库中,则可以不修改这里。
react 源码在项目中无法直接使用,这里需要稍微修改下。
注意,我这里的 React 的版本是18.1.0
;若是其他版本,修改方式可能会有些差异。
请注意 React 版本上的差异!
在.eslintrc.js
中,
- 把 extends: ['fbjs', 'prettier'] 的数组设置为空;
- plugins 中的 react 注释掉;
- rules 中的
no-unused-vars
设置为 OFF; - rules 中的
react-internal/no-production-logging
设置为 OFF;
具体如下:
// 我们忽略其他未修改的属性
module.exports = {
extends: [], // ['fbjs', 'prettier'], debug-react 的需要
plugins: [
'jest',
'no-for-of-loops',
'no-function-declare-after-return',
'react',
// 'react', // debug-react 的需要
'react-internal',
],
rules: {
'no-unused-vars': OFF, // [ERROR, {args: 'none'}], debug-react 的需要
'react-internal/no-production-logging': OFF, // ERROR, debug-react 的需要
},
};
后续在调试的过程,若还有其他 eslint 方面的报错,可以在这个文件里将其对应的规则关闭掉,然后重启即可。
新增如下代码:
export {
unstable_flushAllWithoutAsserting,
unstable_flushNumberOfYields,
unstable_flushExpired,
unstable_clearYields,
unstable_flushUntilNextPaint,
unstable_flushAll,
unstable_yieldValue,
unstable_advanceTime,
unstable_setDisableYieldValue,
} from './src/forks/SchedulerMock';
注释掉 throw error 的代码,并新增 export 的代码:
// throw new Error('This module must be shimmed by a specific renderer.');
export * from './forks/ReactFiberHostConfig.dom';
注释掉 import 和 const 声明的代码,重新进行 import 引入:
// import * as React from 'react';
// const ReactSharedInternals =
// React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
import ReactSharedInternals from '../react/src/ReactSharedInternals';
export default ReactSharedInternals;
设置默认导出,源码中只有 export 的方式,若外部直接使用,需要用* as React
这种格式导出成全局变量。React 源码中也有解释:
// Export all exports so that they're available in tests.
// We can't use export * from in Flow for some reason.
在 Flow 语法中,我们无法用 export *
的这种方式来导出所有方法。
因此这里我们单独添加一个默认导出。
// 在文件的最底部
import * as React from './src/React';
export default React;
同上面的 react 原因,这里我们修改下 ReactDOM:
// 在文件的最底部
const ReactDOM = { createRoot, hydrateRoot };
export default ReactDOM;
cra 的脚手架也需要稍微修改下。配置修改对应的 commit:chore(config): update config to load react source
react 源码中有不少的全局变量,如__DEV__
等,这里我们需要在config/env.js
中添加上,否则会提示找不到这个全局变量。
注意,我们回到了最外层的 debug-react 项目了,是修改的用yarn eject
弹出的配置。我们在变量 stringified 中添加下述变量:
// config/env.js
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
// 新增全局变量
__DEV__: true,
__PROFILE__: true,
__UMD__: true,
__EXPERIMENTAL__: true,
__VARIANT__: false,
// 新增全局变量结束
};
修改 webpack 配置中的别名 alias,用于调整引入的 React, ReactDOM 的引用位置。
修改的文件: config/webpack.config.js
// config/webpack.config.js
module.exports = function () {
return {
// 新增别名
resolve: {
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.resolve(__dirname, '../src/react/packages/react'),
'react-dom': path.resolve(__dirname, '../src/react/packages/react-dom'),
shared: path.resolve(__dirname, '../src/react/packages/shared'),
'react-reconciler': path.resolve(__dirname, '../src/react/packages/react-reconciler'),
scheduler: path.resolve(__dirname, '../src/react/packages/scheduler'),
'react-devtools-scheduling-profiler': path.resolve(
__dirname,
'../src/react/packages/react-devtools-scheduling-profiler',
),
'react-devtools-shared': path.resolve(__dirname, '../src/react/packages/react-devtools-shared'),
'react-devtools-timeline': path.resolve(__dirname, '../src/react/packages/react-devtools-timeline'),
// 新增的 alias 结束
},
},
};
};
到这里我们基本上就可以改造完毕了,启动项目就可以运行起来。
$ npm start
我们需要在 react 源码中调试或者输出一些 log 时,就可以直接修改了。