React 之如何调试源码

简介: React 源码如何调试,想必大家在阅读源码的时候一定会遇到,所以本篇我们来讲讲如何进行源码调试。

官方推荐


其实 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,如果构建的时候报了这样的错误:

image.png

就说明 JDK 还没有安装,点击跳转安装后,再次尝试构建。


4. link 文件

// 进入 react-app 项目目录后
yarn link react react-dom
// 启动项目
yarn start

启动后查看源代码,我们会发现 react、react-dom 已经 link 到我们构建的 react 文件上

image.png


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 个编译错误……

image.png

但不用害怕,我们慢慢解决。


有一类报错是这样的:

image.png

这类问题都可以通过 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 个报错了:

image.png


5. 错误 1:修改 React 和 ReactDOM 引入方式


在这剩余的 5 个报错中,有一个报错是:

image.png

为了避免这个错误,我们打开 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


有两个报错如下:

image.png

按照指示,我们打开 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


还有一个报错是:

image.png

为了简单起见,我们直接关掉 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:环境变量


到这里应该就没有编译错了,但是重新运行后,页面空白,打开控制台,我们会看到报错:

image.png

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


还有报错:

image.png

首先找到报错文件: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


还有报错:

image.png

打开 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);
}

可以看到浏览器打印了:

image.png

目录
相关文章
|
6月前
|
Web App开发 存储 前端开发
React 之 Scheduler 源码中的三个小知识点,看看你知不知道?
本篇补充讲解 Scheduler 源码中的三个小知识点。
118 0
|
1月前
|
移动开发 JSON 数据可视化
精选八款包括可视化CMS,jquery可视化表单,vue可视化拖拉,react可视化源码
精选八款包括可视化CMS,jquery可视化表单,vue可视化拖拉,react可视化源码
43 0
|
5月前
|
前端开发 资源调度
如何本地 Debug React 源码
如何本地 Debug React 源码
38 3
|
6月前
|
前端开发 JavaScript 安全
React 之 createElement 源码解读
React 之 createElement 源码解读
65 0
|
6月前
|
JSON 前端开发 JavaScript
React源码解析-JSX
React源码解析-JSX
110 1
|
6月前
|
前端开发 JavaScript 测试技术
10个yyds的Vue、React源码解析开源项目
10个yyds的Vue、React源码解析开源项目
|
6月前
|
前端开发 JavaScript API
React 之 Refs 的使用和 forwardRef 的源码解读
React 之 Refs 的使用和 forwardRef 的源码解读
71 1
|
6月前
|
前端开发 JavaScript API
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
112 0
|
6月前
|
前端开发 安全 调度
React 之 Scheduler 源码解读(下)
本篇我们接着《React 之 Scheduler 源码解读(上)》,讲解延时任务的执行源码。
76 0
|
6月前
|
前端开发 JavaScript API
React 之 Scheduler 源码解读(上)
React 之 Scheduler 源码解读(上)
108 0