Skip to main content

​本文会介绍React 对象中与组件相关的 API 的实现,以及各种类型的组件与ReactElement的关系。

故事的开始从一行代码说起

import React from 'react';

我们从react这个包中获得了React这样一个对象,通过查看React 顶层 API我们可以构建如下React对象:

const React = {
// 组件
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
// ReactElement
createElement: createElement,
cloneElement: cloneElement,
createFactory: createFactory,
isValidElement: isValidElement,
Children: {
map,
forEach,
count,
toArray,
only,
},
// hooks
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState
};

React 的编译时与运行时

我们首先介绍一下React的编译时与运行时。

class App extends React.Component {
render() {
return <ClassType />
}
}
class ClassType extends React.Component {
render() {
return <div>123</div>
}
}

上面一段代码,在利用babeljsx编译成js之前是无法直接在浏览器中执行的。在经过 @babel/babel-transform-react-jsx的编译之后会得到如下结果:

class App extends React.Component {
render() {
return /*#__PURE__*/React.createElement(ClassType, null);
}
}
class ClassType extends React.Component {
render() {
return /*#__PURE__*/React.createElement("div", null, "123");
}
}

ReactElement与组件的关系

大家都知道组件分为类组件与函数组件,那么React 中到底有哪些组件呢?

  • React.Component 的子类组件
  • React.PureComponent 的子类组件
  • 普通的函数组件
  • React.memo高阶组件
  • React.Fragment 组件
  • React.lazy懒加载组件
  • React.forwardRef组件
  • React.createContext().Provider以及React.createContext().Consumer组件
  • React.Suspense组件

从上面运行时代码可以看到jsx被编译成了React.createElement的调用形式。那么ReactElement与组件的关系到底是怎样的呢?先看下面的代码:

React.createElement(ClassType, null);

可以看到ClassType这个类(类构造函数)被当作了createElement的第一个参数,而createElement则是专门用来生成ReactElement的函数。在上一篇文章中我们提到:ReactElement.type代表了【行为】,ReactElement.$$typeof代表了【组件类型】,并且ReactElement.type有如下值:

在调用 createElement 创建 ReactElement 的时候,传入的第一个参数为 type 属性的值,如果是字符串比如'div',则表示该react实例对应一个真实的dom;如果是一个函数,则表示一个函数/类组件;也可能是一个对象(typeof === 'object'),比如 Context.ProviderContext.ConsumerReact.lazyReact.forwardRefReact.memo

那么我们可以做如下总结:你所写的组件的构造函数或者说函数都会被挂在ReactElement.type属性上,React可以在合适的时机通过它来执行特定的函数,从而表现出你想要的行为。

思考:什么时候会执行这些构造函数或者函数呢?这个后续文章会说明,等不及的可以先去图里updateClassComponent函数。

类组件超类 React.Component 与React.PureComponent

趁热打铁,接着上边罗列出来的组件种类,先来说说类组件,React.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) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

React.PureComponent通过寄生组合式继承来实现:

function ComponentDummy(){};
ComponentDummy.prototype = Component.prototype;
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;

此外还有一个地方需要注意,直接将超类Component的原型方法复制到了自己的原型对象上,较少查找次数:

Object.assign(pureComponentPrototype, Component.prototype);

updater

在类组件的实例属性中有个updater有点奇怪,我们在写组件的时候,构造函数只传入了两个参数, propscontext,第三个参数始终是undefined,这个地方需要注意了,组件处理state的逻辑是由updater提供的,React 内部在实例化组件之后,会立即给updater赋值为constructClassInstance,可以先去图里updateClassComponent函数。 updater 对象如下:

const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {},
enqueueReplaceState(inst, payload, callback) {},
enqueueForceUpdate(inst, callback) {},
};

三个方法的实现基本一致,都会先计算一个【到期时间】,然后生成一个 update 更新对象,接着将该对象添加到fiber上的update单向环形链表中(在React中,由于既保存了链表头节点又保存了尾节点,所以没有体现环形单链的优势。如果在只保存一个节点的条件下,显然保存环形链表的尾节点则可实现O(1)的队列),最后开始调度fiber树,进行新一轮的更新。

实现React自定义标签组件 Fragment,StrictMode,Fragment

这三个API的值都是symbole类型,都能够直接用做一个 React 标签组件。与原生标签divHostComponent)类似,其对应的 ReactElement.type都是不是函数,React 会根据该值调用不同的方法来创建 fiber,比如:

function createFiberFromFragment(){}
function createFiberFromMode
function createFiberFromSuspense

因此这里做好标记即可

const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment');
const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode');
const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
const React = {
// 组件
Component,
PureComponent,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
...
};

ReactElement.type 为对象

当你使用React.forwardRef,React.memo,React.lazy,React.createContext这四个高阶函数生成组件的时候,ReactElement.type为一个对象,必定包含$$typeof属性:

// `React.forwardRef` 
return {
$$typeof: REACT_FORWARD_REF_TYPE,
render, // (props, ref) => React$Node
};
// `React.memo`
return {
$$typeof: REACT_MEMO_TYPE,
type,
compare: compare === undefined ? null : compare, // (oldProps, newProps) => boolean
};
// `React.lazy`, e.g. React.lazy(() => import('path/to/Disc'))
return {
$$typeof: REACT_LAZY_TYPE, //REACT_LAZY_TYPE组件类型
_ctor: ctor, //动态加载逻辑
// React uses these fields to store the result.
_status: -1,
_result: null,
};
// `React.createContext`
return const context = {
$$typeof: REACT_CONTEXT_TYPE,
Provider: {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
},
Consumer: context,
};

这样我们也可以定制一些自己需要的组件,除了在此处增加不同的$$typeof之外,还需增加对应的createFiber的方法。

注意:memo与forwardRef组合使用的时候,正确的方式为:memo(forwardRef(...)),因为forwardRef需要接收一个render函数,该render函数为(props, ref) => React$Node。

总结

​ 本文介绍了,React 对象中与组件相关的 API 的实现,聊了各种组件种类与ReactElement的关系。下一篇将会介绍如何创建一个 ReactElement,以及 Children 各个方法的实现原理。