React 源码解析之总览

4,511 阅读5分钟

平常我们对外(后端、产品或其他前端)总喜欢说用的是 React 框架,可是我们并不都熟悉 React 内部是怎么运行的。事实上,当 Facebook 将 React 和 ReactDOM 分包发布后,React 就不仅仅是前端框架了,15版本后 react 源码越来越少,而 React-dom 却很大。很显然,react 的很多逻辑都移到 react-dom 中了。

版本说明

先说下 React16 这个版本节点吧。

React16 较之前的版本是核心上的一次重写(想想就疯狂),虽然之前 API 没有变化继续使用,但同时也增加了很多好用的功能(不然不是白瞎了么)。这也是首次引入 Fiber 概念,之后新的功能都是围绕 Fiber,比如 AsyncModeProfiler 等。

说明:后面章节贴代码的部分,我都会删除原来英文注释,加上自己的理解,有问题的地方,还请在评论中指出(如果有评论的话,没有评论可以加我,有朋自远方来...)

上代码

看看截止目前为止,React 暴露出来的 API

// react/packages/react/index.js
'use strict';
const React = require('./src/React');
module.exports = React.default || React;

// react/packages/react/src/React.js
...

const React = {
  // packages/react/src/ReactChildren.js
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  // packages/react/src/ReactCreateRef.js
  createRef,
  // packages/react/src/ReactBaseClasses.js
  Component,
  PureComponent,

  // packages/react/src/ReactContext.js
  createContext,
  // packages/react/src/forwardRef.js
  forwardRef,
  lazy,
  memo,

  // packages/react/src/ReactHooks.js
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,

  // packages/shared/ReactSymbols.js
  Fragment: REACT_FRAGMENT_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  // packages/react/src/ReactElementValidator.js
  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  // packages/shared/ReactVersion.js
  version: ReactVersion,

  // packages/shared/ReactSymbols.js
  unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
  unstable_Profiler: REACT_PROFILER_TYPE,

  // packages/react/src/ReactSharedInternals.js
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};

...

上面代码我们选择性的去解析,无需所有都要去了解(个人学习方法方式)。

先说 Children

这个对象提供了一些处理 props.children 方法,children 是一个类似数组但又不是数组的数据结构,对其进行处理时可用 React.Children 外挂方法。

createRef

ref 用法,不推荐使用 string ref 用法,比如 <div ref="divRef" />。那正确姿势是怎样的呢?

class App extends React.Component {
  constructor() {
    this.ref = React.createRef();
  }

  render() {
    return <div ref={this.ref} />
    // or
    return <div ref={node => this.ref = node} />
  }
}

Component 和 PureComponent

packages/react/src/ReactBaseClasses.js 代码

import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
const emptyObject = {};

// Component
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {}
Component.prototype.forceUpdate = function(callback) {}

// ComponentDummy => Component仿制品
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

// PureComponent
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true; // 多了一个标识

export { Component, PureComponent };

这两个类基本相同,唯一区别是 PureComponent 的原型上多了一个标识 isPureReactComponent

if (ComponentExample.prototype && ComponentExample.prototype.isPureReactComponent) {
  return (
    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
  );
}

这是检查组件是否需要更新的一个判断,ComponentExample 是你声明的继承自 ComponentPureComponent 的类,他会判断你是否继承自 PureComponent,如果是的话就用 shallowEqual 比较 stateprops

By the way(顺便说一下,允许我骚一下):React 中对比一个 ClassComponent 是否需要更新,只有两个地方。

一是看有没有 shouldComponentUpdate 方法;二就是这里的 PureComponent 判断;

createContext

createContext 是官方定稿的 context 方案,在这之前我们一直在用的老的 context API ,也是 React 不推荐的 API。Now(现在),新的 API 出来了,官方也已经确定在 17 大版本会把老 API 去除。

新 API 的使用方法

const { Provider, Consumer } = React.createContext('defaultValue');

const ProviderComp = props => (
  <Provider value='realValue'>
    {props.children}
  </Provider>
);

const ConsumerComp = () => (
  <Consumer>
    {value => <p>{value}</p>}
  </Consumer>
)

具体差异,后面讲 context 环节会详细指出。

forwardRef

forwardRef 是用来解决 HOC 组件传递 ref 的问题的,所谓 HOC 就是 Higher Order Component。就拿redux 来说,使用 connect 来给组件绑定需要的 state,这其中其实就是给我们的组件在外部包了一层组件,然后通过 ...props 的方式把外部的 props 传入到实际组件。forwardRef 的使用方法如下:

const TargetComponent = React.forwordRef((props, ref) => {
  <TargetComponent ref={ref} {...props} />
});

这也说明了为什么要提供 createRef 作为新的 ref 使用方法的原因,如果用 string ref 就没法当作参数传递了。

后面章节会详细分析 ref

lazy

是用来实现异步加载模块的功能。

memo

是一个高阶函数,它与 React.PureComponent类似,但是一个函数组件而非一个类。

useXXX 系列

这就是 React16 的 Hooks 了,后续会做代码分解。

类型

Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,

这 5 个都是 React 提供的组件,但他们呢其实都只是占位符,都是一个 Symbol,在 React 实际检测到他们的时候会做一些特殊的处理,比如 StrictModeAsyncMode 会让他们的子节点对应的 Fiber 的 mode 都变成和他们一样的mode

元素

createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,

createElement

是 React 输出中最重要的 API 了,是用来创建 ReactElement 的,但是很多前端童鞋却从没见过,也没用过,这是为什么呢?这就得感谢 JSX 了,我们知道 JSX 并不是标准的 js,所以要经过编译才能变成可运行的 js,而编译之后,createElement 就出现了:

// jsx
<div id="app">content</div>

// js
React.createElement('div', { id: 'app' }, 'content')

cloneElement

它就很明显了,是用来克隆一个 ReactElement

createFactory

它是用来创建专门用来创建某一类 ReactElement 的工厂的

export function createFactory(type) {
  const factory = createElement.bind(null, type);
  factory.type = type;
  return factory;
}

其实就是绑定了第一个参数的 createElement,一般我们用 JSX 进行编程的时候不会用到这个 API。

isValidElement

是用来验证是否是一个 ReactElement 的,基本也用不太到。

你可以...

上一篇:React 源码解析之唠叨两句

下一篇:React 源码解析之ReactElement