前言
阅读源码时,有许多变量在程序运行过程中不断的产生,其中存放着什么东西,一直是一个比较头疼的问题。不停的推导增加了验算的负担,随着代码逐渐的深入,也会产生一定的记忆负担。如果靠脑袋去记,简单点的代码还好。复杂的代码。。。你懂的。
随着 react 被广泛使用,很多人会好奇 react 是怎么实现的。会有一探源码的想法。如果直接阅读 react.development.js 是很简单,页面引入就好了。但是 react.development.js 终于是经过编译工具编译过的代码,很多的代码看起来并不直观。理想的情况是直接引用源文件,也就是 github 上 react 仓库中,packages 目录下的代码,直接阅读 es6 的代码。
但是 es6 代码浏览器支持并不友好。所以需要配置 webpack 打包成 es5。同时需要配上 sourceMap。这样,既可以让源码跑在浏览器环境,也可以直接读 es6 的代码,而且可以随时打断点,查看变量里保存的值。
那么,闲言少叙,开始本章的主题。
参考资料
在配置调试环境的过程中,参考了许多相关资料,这里先列出来,感兴趣的同学可以参考。
-
主流程相关的资料, 点击跳转, 同时致敬以下用户
- 知乎用户“AirCloud”
- github 用户“JesseZhao1990”
- github 用户 ”jsonz1993“
- github 用户 ”nannongrousong”
-
主流程无关但是收益颇丰的资料
- 以前的这篇文章的重写
- react 官方的 codebase-overview
- react 仓库代码的一次重要变更,这个放后面讲
- 一个报错函数的修改
正文
本人所在测试环境为 mac,其他环境类似, 调试版本 React Version 16.9.0
需要准备的一些环境:Node/npm/create-react-app/git。
ps:本文是对参考资料的梳理以及优化,力求言简意赅。
- Fork react 的仓库到自己的 git 上。(为了方便自己做记录,fork 后自己的 git 上也会有 react,改完后可以往自己的这个上 push,如果是 clone,则没有权限 push, 例如我 fork 出来的地址为
git@github.com:pws019/react.git
). -
npx create-react-app my-app
(利用create-react-app
创建自己的 demo 项目) -
cd my-app
(进入上一步创建出来的 my-app 目录) -
yarn run eject
(将 webpack 的配置提取出来, 执行完后项目中会多一个 config 文件夹,存放 webpack 相关脚本) - 进入到项目的
src
目录,git clone git@github.com:pws019/react.git(替换为你刚 fork 的 react 路径)
- 为
/config/webpack.config.js
中的resolve
选项增加alias
如下(为了让项目中的引用的 react 是源码包里的 react):
({
xxxx: 'xxx',
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',
'react': path.resolve(__dirname, '../src/react/packages/react'),
'react-dom': path.resolve(__dirname, '../src/react/packages/react-dom'),
'legacy-events': path.resolve(__dirname, '../src/react/packages/legacy-events'),
'shared': path.resolve(__dirname, '../src/react/packages/shared'),
'react-reconciler': path.resolve(__dirname, '../src/react/packages/react-reconciler'),
// 'react-events': path.resolve(__dirname, '../src/react/packages/events'),
// 'scheduler': path.resolve(__dirname, '../src/react/packages/scheduler'),
},
},
xxxx: 'xxx',
})
- 将上一步文件中的
devtool
的值改为source-map
(为了让打出的包有 sourcemap) - 为
/config/env.js
中的stringifed
对象增加属性:
const stringified = {
'xxxx': 'xxx',
"__DEV__": true,
"__PROFILE__": true,
"__UMD__": true
};
- 由于 react 的源码中采用了
flow
这个东东做类型检查,执行yarn add @babel/plugin-transform-flow-strip-types -D
,安装对应的 babel 插件忽略 flow 的类型检查,并且在 webpack 的babel-loader
中增加该插件
{test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve('babel-preset-react-app/webpack-overrides'),
plugins: [
[require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent:
'@svgr/webpack?-svgo,+titleProp,+ref![path]',
},
},
},
],
[require.resolve('@babel/plugin-transform-flow-strip-types')] //************* 这一行是新加的
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
cacheCompression: isEnvProduction,
compact: isEnvProduction,
},
},
- 注释掉
webpack
中module.rules[1]
,也就是 eslint 的配置,因为我也没搞明白,怎么配,总报错。 -
这时候执行
npm start
启动项目,会发现报错,主要有三处,依次解决之。- 修改文件
/src/react/packages/react-reconciler/src/ReactFiberHostConfig.js
。注释中说明,这块还需要根据环境去导出 HostConfig。
export * from './forks/ReactFiberHostConfig.dom';
- 修改文件
/src/react/packages/shared/ReactSharedInternals.js
。react 此时未 export 内容,直接从 ReactSharedInternals 拿值
// import React from 'react'; // const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; import ReactSharedInternals from '../react/src/ReactSharedInternals';
- checkReact 文件报错,是由于
src/react/packages/shared/invariant
中的函数,直接抛错,这里纠结了好久才找到解法,改下这个函数的内容为
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.', ); }
- 修改文件
花絮
配这个环境的时候,github 用户 nannongrousong
的那篇文章给了我很多帮助,基本是按着他的配的,但是在最后,总是在 react-dom 的 checkReact 中总报错,这个问题困扰了我很久。
后来突然想起来,官方的文档里提到 当 invariant 判别条件为 false 时,会将 invariant 的信息作为错误抛出, 而我调试的那个地方,值为 true 依然报错了。
后来查看了 github 用户 nannongrousong
提供的调试环境成品库,主要差距就是 src/react/packages/shared/invariant
里函数的实现。
后来我去翻了该文件的commit history
,发现了这个文件的改动历史,发现是由于有同学想减少包的大小,更好的归拢错误提示,而吧这里抽出来,由自动化工具去替换。
感兴趣的可以看下这里,
通过这里的相关代码,可以看到,他是利用 ast 语法树的分析 & 替换,去通过工具处理了错误,这个思路值得我们学习借鉴。
已经搭建好的项目
点我跳转
这个仓库是已经配好的环境,安装完依赖包就可以开始。