react源码学习环境搭建

20次阅读

共计 4059 个字符,预计需要花费 11 分钟才能阅读完成。

前言

阅读源码时,有许多变量在程序运行过程中不断的产生,其中存放着什么东西,一直是一个比较头疼的问题。不停的推导增加了验算的负担,随着代码逐渐的深入,也会产生一定的记忆负担。如果靠脑袋去记,简单点的代码还好。复杂的代码。。。你懂的。
随着 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,
    },
},
  • 注释掉 webpackmodule.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 语法树的分析 & 替换,去通过工具处理了错误,这个思路值得我们学习借鉴。

已经搭建好的项目

点我跳转
这个仓库是已经配好的环境,安装完依赖包就可以开始。

正文完
 0