阅读 797

React17 源码解析(开篇) —— 搭建 react 源码调试环境

作为前端最优秀的开源框架之一,react 值得每一个前端开发者学习。今年花了很长的一段时间,反复理解品读了 react 的源码,感觉收获颇丰,趁着国庆假期打算开始写一系列 react v17 源码解析的文章和大家一起讨论学习。

通过源码的学习,你可以提升项目中排查 bug 的能力、更好的理解 react 的工作过程和工作模式、提升数据结构和算法设计的能力,以及最重要的:提升面试竞争力。

系列目录

本系列的源码解析的 react 版本是 v17.0.2,将从 react 应用的入口开始讲解,涉及到一个较为完整的 react 依赖的绝大部分的主要功能和核心,大体的内容纲要如下:

  • jsx 和 React.createElement

  • ReactDOM.render

  • fiber 架构

  • concurrent mode

  • render 和 commit 阶段

  • reconciliation 和 diff 算法

  • hooks 源码

由于上面的章节并未完结,后续实际写的过程中可能略有调整,本篇章会随时更新,感兴趣的看官老爷建议收藏一下本章作为目录导航。

搭建 react 源码调试环境

本系列章节除了源码的解析之外,也希望大家能够自己学会如何阅读源码,既然想要阅读源码,那么必然少不了对源码的调试,下面会讲解如何搭建一个 react 源码的调试环境。

创建项目

首先通过官方脚手架 create-react-app 创建一个 react 项目,在终端执行以下命令:

npx create-react-app debug-react 复制代码

暴露 webpack 配置

我们后续想要对通过直接引入 react 源码替代 node_modules 中的 react 包,需要修改 webpack,在 debug-react 目录下执行以下命令暴露 webpack 配置:

cd ./debug-react yarn eject 复制代码

Are you sure you want to eject? This action is permanent. 这里选择 y 就可以,命令执行完毕后在新增的 config 文件夹下可以看到 webpack 的配置文件:

 

引入 react 源码及修改 webpack

由于 node_modules 中的 react 包是打包好之后的文件,许多代码掺杂在一个文件中,不便于我们对源码进行调试。因此在 debug-react 的 src 目录下引入 react 的源码:

git clone https://github.com/facebook/react.git -b 17.0.2 复制代码

并在刚刚引入的 src/react 目录下执行一下命令安装依赖:

yarn install 复制代码

然后我们修改 webpack 的配置,使得在代码中引入的 react 等 npm 包的指向由 node_modules 改为刚刚引入的源码。在 config/webpack.config.js 下新增如下几个包的引用:

// ... module.exports = {     // ...     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 || {}), +       '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'),       },     } } 复制代码

修改环境变量

我们将 __DEV__ 等环境变量默认启用,便于开发调试,修改 config/env.js

// ... function getClientEnvironment(publicUrl) {   // ...   const stringified = { +   __DEV__: true, +   __PROFILE__: true, +   __UMD__: true, +   __EXPERIMENTAL__: true,     'process.env': Object.keys(raw).reduce((env, key) => {       env[key] = JSON.stringify(raw[key]);       return env;     }, {}),   };   return { raw, stringified }; } 复制代码

在 debug-react 的根目录下创建 .eslintrc.json 文件,内容如下:

{   "extends": "react-app",   "globals": {     "__DEV__": true,     "__PROFILE__": true,     "__UMD__": true,     "__EXPERIMENTAL__": true   } } 复制代码

解决一系列报错

上面的环境配置好之后,通过 yarn start 启动会出现一系列的报错问题,因为 react 中某些遍历是在打包时根据环境注入生成的,我们现在要直接调试源码,不进行 react 的打包,所以要解决这些报错。下面直接讲问题解决了。

添加 ReactFiberHostConfig 引用

如下报错

Attempted import error: 'afterActiveInstanceBlur' is not exported from './ReactFiberHostConfig'. 复制代码

解决方式

直接修改 src/react/packages/react-reconciler/src/ReactFiberHostConfig.js 的内容如下:

- import invariant from 'shared/invariant'; - invariant(false, 'This module must be shimmed by a specific renderer.'); + export * from './forks/ReactFiberHostConfig.dom' 复制代码

另外修改 src/react/packages/shared/ReactSharedInternals.js,直接从引入 ReactSharedInternals 并导出:

- import * as React from 'react'; - const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + import ReactSharedInternals from '../react/src/ReactSharedInternals'; 复制代码

修改 react 引用方式

如下报错

Attempted import error: 'react' does not contain a default export (imported actFiberHosts 'React'). 复制代码

解决方式

修改 src/index.js 中 react 和 react-dom 的引入方式:

- import React from 'react'; - import ReactDOM from 'react-dom'; + import * as React from 'react'; + import * as ReactDOM from 'react-dom'; 复制代码

修改 inveriant

如下报错

Error: Internal React error: invariant() is meant to be replaced at compile time. There is no runtime version. 复制代码

解决方式

修改 src/react/packages/shared/invariant.js 的内容:

export default function invariant(condition, format, a, b, c, d, e, f) { + if (condition) { +   return; + }   throw new Error(     'Internal React error: invariant() is meant to be replaced at compile ' +       'time. There is no runtime version.',   ); } 复制代码

解决 eslint 报错

还剩下一堆有关 eslint 的报错,诸如:

Failed to load config "fbjs" to extend from. 复制代码

解决方式

eslint 报错的内容实在太多了,我这里直接简单粗暴的将 webpack 中 eslint 插件给关掉,修改 src/config/webpack.config.js 文件:

module.exports = {   // ...   plugins: [     // ... -   !disableESLintPlugin && -   new ESLintPlugin({ -   // Plugin options -     extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'], -     formatter: require.resolve('react-dev-utils/eslintFormatter'), -       eslintPath: require.resolve('eslint'), -     failOnError: !(isEnvDevelopment && emitErrorsAsWarnings), -     context: paths.appSrc, -     cache: true, -     cacheLocation: path.resolve( -       paths.appNodeModules, -       '.cache/.eslintcache' -     ), -     // ESLint class options -     cwd: paths.appPath, -     resolvePluginsRelativeTo: __dirname, -     baseConfig: { -       extends: [require.resolve('eslint-config-react-app/base')], -       rules: { -          ...(!hasJsxRuntime && { -          'react/react-in-jsx-scope': 'error', -       }), -     }, -   }, -   }),   ] } 复制代码

总结

至此,我们的调试环境就搭建完成了,可以在 react 源码中通过 debugger 打断点或者 console.log() 输出日志进行愉快地调试了!


作者:Dwan
链接:https://juejin.cn/post/7014775797596553230


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐