本文会介绍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函数(GitHub: https://github.com/BUPTlhuanyu/ReactNote 内附【20Mb】 React 原理图)。
类组件超类 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函数(GitHub: https://github.com/BUPTlhuanyu/ReactNote 内附【20Mb】 React 原理图)。
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 createFiberFromModefunction 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 各个方法的实现原理。
- 2024年03月02日
- 星期六