本文会介绍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>
}
}
上面一段代码,在利用babel
将jsx
编译成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.Provider
,Context.Consumer
,React.lazy
,React.forwardRef
,React.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
有点奇怪,我们在写组件的时候,构造函数只传入了两个参数, props
与context
,第三个参数始终是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 标签组件。与原生标签div
(HostComponent
)类似,其对应的 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
各个方法的实现原理。