• home > webfront > ECMAS > react >

    React16源码分析(1):react项目架构/文件目录/包结构解读

    Author:[email protected] Date:

    选取react的 16 7 0版本作为示例,解读源码。本篇解读react 包的整体架构。接着从react development js的1810行,看react的整体结构。在下篇讲解该js文件的核心方法

    选取react的 16.7.0版本作为示例,解读源码

    https://github.com/facebook/react/tree/master/packages 分析react的整体结构。

    react结构剖析

    React源码结构树:

    React源码结构树

    React16的架构分为三层:

    1. Scheduler(调度器):调度任务的优先级,高优任务优先进入Reconciler

    2. Reconciler(协调器):负责找出变化的组件

    3. Renderer(渲染器): renderers一直是React代码的核心部分,它包含了大部分功能的实现。

    renderers源码目录:

    renderers源码目录

    任务调度(Scheduler)阶段有两个性能的优化点

    • 将任务队列的内部数据结构转换成最小二叉堆的形式以提升队列的性能(在最小堆中我们能够以最快的速度找到最小的那个值,因为那个值一定在堆的顶部,有效减少整个数据结构的查找时间)。

    • 使用周期更短的postMessage循环的方式而不是使用requestAnimationFrame这种与帧边界对齐的方式(这种优化方案指得是在将任务进行延迟后恢复执行的阶段,前后两种方案都是宏任务,但是宏任务也有顺序之分,postMessage的优先级比requestAnimationFrame高,这也就意味着延迟任务能够更快速地恢复并执行)。


    React的核心

    包含所有全局 React API,如:

    • React.createElement

    • React.Component

    • React.Children

    这些API是全平台通用的,它不包含ReactDOM、ReactNative等平台特定的代码。

    packages目录

    • scheduler文件夹 : Scheduler(调度器)的实现。

    • shared文件夹 :源码中其他模块公用的方法和全局变量,比如在shared/ReactSymbols.js中保存React不同组件类型的定义。

    • legacy-events文件夹:React的合成事件机制。

    • Renderer相关的文件夹:渲染相关

      • - react-art 

      • - react-dom                 # 注意这同时是DOM和SSR(服务端渲染)的入口

      • - react-native-renderer

      • - react-noop-renderer       # 用于debug fiber(后面会介绍fiber)

      • - react-test-renderer

    • 试验性包的文件夹:React将自己流程中的一部分抽离出来,形成可以独立使用的包,由于他们是试验性质的,所以不被建议在生产环境使用。包括如下文件夹:

      • - react-server        # 创建自定义SSR流

      • - react-client        # 创建自定义的流

      • - react-fetch         # 用于数据请求

      • - react-interactions  # 用于测试交互相关的内部特性,比如React的事件模型

      • - react-reconciler    # Reconciler的实现,你可以用他构建自己的Renderer

    • 辅助包的文件夹:React将一些辅助功能形成单独的包。包括如下文件夹:

      • - react-is       # 用于测试组件是否是某类型

      • - react-client   # 创建自定义的流

      • - react-fetch    # 用于数据请求

      • - react-refresh  # “热重载”的React官方实现

    • react-reconciler文件夹:我们需要重点关注react-reconciler,在接下来源码学习中80%的代码量都来自这个包。

    react源码目录结构

    react的 16.7.0 node_modules/react

    react 入口

    进入react 目录,index.js

    if (process.env.NODE_ENV === 'production') {
      module.exports = require('./cjs/react.production.min.js');
    } else {
      module.exports = require('./cjs/react.development.js');
    }

    如果不是生产环境,就走react.development.js。纯代码也就一千多行的样子

    看下这个JS,首先通过symbol 定义各种类型

    // TODO: this is special because it gets imported during build.
    var ReactVersion = '16.7.0';
    // The Symbol used to tag the ReactElement-like types. If there is no native Symbol
    // nor polyfill, then a plain number is used for performance.
    var hasSymbol = typeof Symbol === 'function' && Symbol.for;
    
    var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
    var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca;
    var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb;
    //……

    到243行,队列更新相关代码

    function warnNoop(publicInstance, callerName) {
      {
        var _constructor = publicInstance.constructor;
        var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass';
        var warningKey = componentName + '.' + callerName;
        if (didWarnStateUpdateForUnmountedComponent[warningKey]) {
          return;
        }
        warningWithoutStack$1(false, "Can't call %s on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName);
        didWarnStateUpdateForUnmountedComponent[warningKey] = true;
      }
    }
    /**
     * This is the abstract API for an update queue.
     */
    var ReactNoopUpdateQueue = {}

    376行,组件相关代码

    react虚拟DOM实现的核心

    import React, {Component} from 'react';
    export default class App extends Component {
        constructor(props) {}
        render() { return (<div>demo<div>)}
    }
    ReactDOM.render(<App />, document.getElementById('root'));

    App组件的render方法返回的是一段HTML结构,在普通的函数中这种写法是不支持的,所以我们一般需要相应的插件来在背后支撑,在React中为了支持这种jsx语法提供了一个Babel预置工具包@babel/preset-react,其中这个preset又包含了两个比较核心的插件:

    • @babel/plugin-syntax-jsx:这个插件的作用就是为了让Babel编译器能够正确解析出jsx语法。

    • @babel/plugin-transform-react-jsx:在解析完jsx语法后,因为其本质上是一段HTML结构,因此为了让JS引擎能够正确识别,我们就需要通过该插件将jsx语法编译转换为另外一种形式。在默认情况下,会使用React.createElement来进行转换,当然我们也可以在.babelrc文件中来进行手动设置。

    转换后

    render() {
        return React.createElement("div", {
            className: "content"
        }, 
        React.createElement("header", null, "React learning"), 
        React.createElement(List, { data: this.state.data }));
    }

    可以看到jsx语法最终被转换成由React.createElement方法组成的嵌套调用链,可能你之前已经了解过这个API,或者接触过一些伪代码实现,这里我们就基于源码,深入源码内部来看看其背后为我们做了哪些事情。

    从1810行,看react的整体结构

    var React = {
      Children: {
        map: mapChildren,
        forEach: forEachChildren,
        count: countChildren,
        toArray: toArray,
        only: onlyChild
      },
    
      createRef: createRef,
      Component: Component,
      PureComponent: PureComponent,
    
      createContext: createContext,
      forwardRef: forwardRef,
      lazy: lazy,
      memo: memo,
    
      Fragment: REACT_FRAGMENT_TYPE,
      StrictMode: REACT_STRICT_MODE_TYPE,
      Suspense: REACT_SUSPENSE_TYPE,
    
      createElement: createElementWithValidation,
      cloneElement: cloneElementWithValidation,
      createFactory: createFactoryWithValidation,
      isValidElement: isValidElement,
    
      version: ReactVersion,
    
      unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
      unstable_Profiler: REACT_PROFILER_TYPE,
    
      __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
    };
    
    // Note: some APIs are added with feature flags.
    // Make sure that stable builds for open source
    // don't modify the React object to avoid deopts.
    // Also let's not expose their names in stable builds.
    
    if (enableStableConcurrentModeAPIs) {
      React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
      React.Profiler = REACT_PROFILER_TYPE;
      React.unstable_ConcurrentMode = undefined;
      React.unstable_Profiler = undefined;
    }
     // 一些有用的React Hooks方法
    if (enableHooks) {
      React.useCallback = useCallback;
      React.useContext = useContext;
      React.useEffect = useEffect;
      React.useImperativeMethods = useImperativeMethods;
      React.useLayoutEffect = useLayoutEffect;
      React.useMemo = useMemo;
      React.useReducer = useReducer;
      React.useRef = useRef;
      React.useState = useState;
    }

    这里我们专门来讲:《React16源码分析(2):react.development.js源码注释


    参考文章:

    React技术揭秘1-4 源码的文件目录 https://juejin.im/post/5ef9b00a6fb9a07e894a9e04

    React学习 -- React源码 https://blog.csdn.net/u014328357/article/details/73201116

    React16源码解读:开篇带你搞懂几个面试考点 https://www.cnblogs.com/tangshiwei/p/12100306.html



    转载本站文章《React16源码分析(1):react项目架构/文件目录/包结构解读》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/jsBase/2016_0520_7834.html

    延伸阅读: