#此文件为方便gitee网站观阅使用专门创建

此笔记文件于某一时间截取复制至此,容易存在更新不及时问题,建议观看同级目录下的笔记文件

截取了上方了React深入学习与源码解析笔记的部分知识点至此,方便网站阅读

本人的React学习笔记分类(也是对应本人技术成长路程):[想快速入门看这部分]、[想对React基础系统全面进行学习的同学看这里]、[对基础学习完成且有了一定开发经验,想尝试解析源码的看这里]

#说明

本笔记为本人深入学习React并尝试阅读理解React源码所记录笔记 —>源码版本16.9.0

建议预备知识:react基础

学习过程及笔记记录时查阅借鉴的相关资料官方文档的源码概览;ILoveDevelop的React 源码解析;知乎的神马翔React专栏全栈潇晨的React系列文章、万字长文+图文并茂+全面解析 React 源码 - render 篇;前端桃园的Deep In React之浅谈 React Fiber 架构;还有公司前辈的技术分享

除此笔记外大家可以看我其他笔记 :全栈笔记数据结构与算法编程_前端开发学习笔记编程_后台服务端学习笔记JavaNodejsJavaScript笔记编程工具使用笔记前端代码规范ES6及后续版本学习笔记Vue笔记整合React笔记微信小程序学习笔记Chrome开发使用及学习笔记 以及许多其他笔记就不一一例举了

一、React基础知识总结

在深入学习前,还是先捋一捋对于React基础知识的理解与总结

如果想看详细的React基础知识点笔记,可以看本人 React系统学习笔记

1、JSX

这东西不用解释我为啥放在第一位说了吧,基本上我们大部分React开发者都是用jsx进行代码编写的

这里不着重讲解基础语法,只是给出自己的理解与总结,有需要了解基础语法的同学 —>点我跳转

Ⅰ-我们认为的JSX是什么?

我们认为的JSX是什么? ===> 类HTML的语法? React里面的模板语法? 语法糖?

  • 其实应该都算是对的,但是有好像不是完全对.如果只讲里面的一点的话又有所欠缺的感觉

JSX是(JavaScript XML)的缩写,其实他本质上还是属于JavaScript,不一定用在React上

  • 此处借用React中文官网的一句话:

React不强制要求使用JSX,但是大多数人发现,在JS代码中将JSX与UI放在一起时,在视觉上会有辅助作用.它还可以使得React显示更多有用的错误和警告信息

  • 它本身可以理解为是一个规范,开发者在JSX的帮助下,避免重复地学习不同框架或者库,因为在JSX的规范中,他产生的结果是一致的.用React的思想来说,JSX最终的作用是把模板语法解析成Component、props、children….等等;而具体怎么利用这些产物,就是不同的框架或者库的特性了,比如下面的一个组件它被解析以后其实产生的是一段代码段,并且有固定的参数位置

README2 - 图1

由上图可以看出JSX的产物(h函数名称和h函数的参数),它很像虚拟DOM

Ⅱ-JSX的产物

① 简单的JSX产物

以下是一个最简单的JSX编译后的结果

README2 - 图2

JSX的产物可以理解是基于JSX代码,利用一个函数模板(如同中图示的[h]函数),生成一段[调用]函数模板,然后里面的函数名可能在不同的框架或者库中是不一样的,他没有实现h函数,需要框架或者库自己实现

可以得出一个大胆的结论:JSX理论上是完全跨平台的,只要有人实现它在对应平台的[h]函数,它甚至可以在任何支持JS语言的平台上运行

② Rreact中的JSX产物

  • 作为React的官方指定语法,JSX允许用户在JS代码中插入HTML代码.但是这种写法浏览器是无法解析的,他们就需要一个转换器

Babel就充当了这样一个角色,他在JSX代码编译时候将其转换成JS文件,这样浏览器就能解析了。

  • JSX有JS和HTMl两种写法,本身就是JS写法的其实是不需要转换的

当然也不能说的这么绝对,有时候Babel会为了兼容性的缘故将高版本的语法翻译到低版本,这部分不在讨论范围。我们要关注的其实是HTMl的处理方式

代码转换示例与解析
  1. 比如下面这行代码:

```jsx

Tom and Jerry

———————-通过Babel转换后生成的代码是:————————————-

React.createElement(“div”, { id: “name” }, “Tom and Jerry”);

  1. > HTML语法转变成了JS语法,简单来说,我们所写的JSX最终变成了JS
  2. > 2. 复杂点的例子
  3. > > ```jsx
  4. <div class='wrapper' id='id_wrapper'>
  5. <span>Tom</span>
  6. <span>Jerry</span>
  7. </div>
  8. ----------- 通过Babel转换后生成的代码是: ---------------------
  9. React.createElement("div", {
  10. class: "wrapper",
  11. id: "id_wrapper"
  12. }, React.createElement("span", null, "Tom"), React.createElement("span", null, "Jerry"));

转换规则是比较简单的,React.createElement的第一个参数是节点类型;第二个参数是该节点的属性,以key:value的形式作为一个对象,后面的所有参数都是该节点的子节点。

  1. 自定义组件

```jsx function Comp() { return ‘

Tom and Jerry
‘ }

——————— 通过Babel转换后生成的代码是: ————————————————-

function Comp() { return ‘

Tom and Jerry
‘; } React.createElement(Comp, null);

  1. > 可以看出,React.createElement的第一个参数变成了一个变量,而不是一个字符串,`尝试将函数Comp首字母小写`
  2. > ```jsx
  3. function comp() { return '<div>Tom and Jerry</div>' }
  4. <comp></comp>
  5. -------------- 通过Babel转换后生成的代码是:-------------------------------------------
  6. function comp() { return '<div>Tom and Jerry</div>'; }
  7. React.createElement("comp", null);

React.createElement的第一个参数又变成了一个字符串。
这也就是我们在React中写组件的时候,为什么会要求首字母大写的原因,Babel在编译的时候会将首字母小写的组件视为原生的HTMl节点进行处理,如果我们将自定义组件首字母小写,后续的程序将无法识别这个组件,最终会报错。

Ⅲ-React为什么选择JSX?

  • 对于一个人喜欢的事物,很多可以用一句话概括:之所以选择X,是因为Y和Z不好,然后X有一个点能吸引你,那么X就是好的

  • 但是放到技术上,要回答好这个问题,就需要先了解React可选的其他解决方案有什么不好的地方

  • 其实相关的方案很多,最直观的就是模板:

其实Vue与Angular都是用的模板语法,他们上手简单这是事实,但是对于React团队来说它并不纯粹!!

它引入了很多新的概念,需要去学习模板指令、模板语法等(如Vue需要理解v-if、v-for等),而JSX就没前者这么复杂,它不需要学习新的开发方式,虽然它也有模板的味道,但它本身能直接支持JS写法(如条件表达式和循环等)

2、ReactElement

为何这个放在JSX下方,因为此知识有jsx做铺垫就容易理解很多,有做对比更易理解

Ⅰ-React.createElement函数

通过Babel编译后的JS代码,频繁出现React.createElement这个函数。这个函数的返回值就是ReactElement,通过上面的示例可以看出,React.createElement函数的入参有三个,或者说三类

① type

type指代这个ReactElement的类型

  1. 字符串比如div,p代表原生DOM,称为HostComponent
  2. Class类型是我们继承自Component或者PureComponent的组件,称为ClassComponent
  3. 方法就是functional Component
  4. 原生提供的Fragment、AsyncMode等是Symbol,会被特殊处理

② config

参照上面Babel编译后的代码,所有节点的属性都会以Key:Value的形式放到config对象中。

③ children

子节点不止会有一个,所以children不只有一个,从第二个参数以后的所有参数都是children,它是一个数组

Ⅱ-ReactElement的结构

  1. [$$typeof] 是一个常量,所有通过React.createElement生成的元素都有这个值。一般使用 React 的组件都是挂到父组件的 this.props.children 上面,但是也有例外,比如要实现一个模态框,就需要将模态框挂载到body节点下,这个时候需要使用ReactDOM.createPortals(child, container)这个函数实现,这个函数生成的$$typeof值就是REACT_PORTAL_TYPE。 —>用于确定是否属于ReactElement
  2. [type]指代这个ReactElement的类型 —>用作判断如何创建节点
  3. [key]和[ref]都是从config对象中找到的特殊配置,将其单独抽取出来,放在ReactElement下
  4. [props]包含了两部分,第一部分是去除了key和ref的config,第二部分是children数组,数组的成员也是通过React.createElement生成的对象 —>新的属性内容
  5. _owner在16.7的版本上是Fiber,Fiber是react16+版本的核心,也是调度算法

这些信息对于后期构建应用的树结构是非常重要的,而React通过提供这种类型的数据,来脱离平台的限制

```javascript const element = { // 这个标签允许我们唯一地将其标识为React元素
$$typeof: REACT_ELEMENT_TYPE,

//属于元素的内置属性
type: type, key: key, ref: ref, props: props,

// 记录负责创建此元素的组件。 _owner: owner, };

  1. > 它就是一个简单的对象,为了看清楚这个对象的创建规则,我们举个例子。 首先是我们写的JSX
  2. > ```jsx
  3. <div class='class_name' id='id_name' key='key_name' ref='ref_name'>
  4. <span>Tom</span>
  5. <span>Jerry</span>
  6. </div>

它会被Babel编译为:

  1. React.createElement("div", {
  2. class: "class_name",
  3. id: "id_name",
  4. key: "key_name",
  5. ref: "ref_name"
  6. }, React.createElement("span", null, "Tom"), React.createElement("span", null, "Jerry"));

它会生成这样一个Element

  1. {
  2. $$typeof: REACT_ELEMENT_TYPE,
  3. type'div'
  4. key: 'key_name',
  5. ref: "ref_name",
  6. props: {
  7. class: "class_name",
  8. id: "id_name",
  9. children: [
  10. React.createElement("span", null, "Tom"),
  11. React.createElement("span", null, "Jerry")
  12. ]
  13. }
  14. _owner: ReactCurrentOwner.current,
  15. }

3、React API 梳理

Ⅰ-暴露出来的API

```jsx // react\src\React.js const React = { Children: { map, forEach, count, toArray, only, },

createRef, Component, PureComponent,

createContext, forwardRef, lazy, memo,

useCallback, useContext, useEffect, useImperativeHandle, useDebugValue, useLayoutEffect, useMemo, useReducer, useRef, useState,

Fragment: REACT_FRAGMENT_TYPE, Profiler: REACT_PROFILER_TYPE, StrictMode: REACT_STRICT_MODE_TYPE, Suspense: REACT_SUSPENSE_TYPE, unstable_SuspenseList: REACT_SUSPENSE_LIST_TYPE,

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

version: ReactVersion,

unstable_withSuspenseConfig: withSuspenseConfig,

__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals, };

  1. > 挑一些常见的分析下用法和源码
  2. <a name="d82896ac"></a>
  3. ### Ⅱ-createRef
  4. > react获得ref的方式有三种
  5. > - String ref 方式(被废弃)
  6. > - callback ref 方式
  7. > - `React.createRef` (16.3版本新增特性)
  8. > 代码举例:
  9. > ```jsx
  10. ----------------------- string ref -------------------------
  11. class App extends React.Component {
  12. componentDidMount() {
  13. this.refs.myRef.focus();
  14. }
  15. render() {
  16. return <input ref="myRef" />;
  17. }
  18. }
  19. ------------------------ callback ref-------------------------
  20. class App extends React.Component {
  21. componentDidMount() {
  22. this.myRef.focus();
  23. }
  24. render() {
  25. return <input ref={(ele) => {
  26. this.myRef = ele;
  27. }} />;
  28. }
  29. }
  30. ---------------------- React.createRef ------------------------
  31. class App extends React.Component {
  32. constructor(props) {
  33. super(props);
  34. this.myRef = React.createRef();
  35. }
  36. componentDidMount() {
  37. this.myRef.current.focus();
  38. }
  39. render() {
  40. return <input ref={this.myRef} />;
  41. }
  42. }

查看 React.createRef 的源码,发现只是生成了一个对象,用于保存 current 的值

  1. // react\src\ReactCreateRef.js
  2. export function createRef(): RefObject {
  3. const refObject = {
  4. current: null,
  5. };
  6. return refObject;
  7. }

React.createRef 并不负责将 dom 节点绑定 current 上面,它只负责生成对应的结构

真正做事的是 react-dom。react 这么拆分,将公共的部分放在 react 中,而与平台相关的单独抽离,比如在移动端,负责将页面元素挂在到 current 字段上的就不是 react-dom 了,而是react-native

Ⅲ-Component & PureComponent

① Component:

Component源码

```jsx // react\src\ReactBaseClasses.js function Component(props, context, updater) { this.props = props; this.context = context; // 如果一个组件有字符串引用,我们将在以后分配一个不同的对象。
this.refs = emptyObject; // 初始化默认更新器,但真正的更新器由 renderer. this.updater = updater || ReactNoopUpdateQueue; }

Component.prototype.setState = function(partialState, callback) { this.updater.enqueueSetState(this, partialState, callback, ‘setState’); };

Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, ‘forceUpdate’); };

  1. > Component 中维护了4个变量,propscontextrefs 以及 updater。前三者都是我们常见的,updater 是与平台相关的,他基本负责了 react 中的所有任务,包括数据更新,界面渲染等一系列的工作。
  2. > 以下是 Component 的数据结构,f 表示函数,省略号表示暂时不用关注
  3. > ```json
  4. {
  5. props: {},
  6. context: {},
  7. refs: {},
  8. updater: {...},
  9. state: null,
  10. __proto__: {
  11. constructor: class Demo,
  12. render: f
  13. __proto__: {
  14. constructor: f,
  15. setState: f,
  16. forceUpdate: f,
  17. isReactComponent: {}
  18. }}}

可以看到,eact 中的声明周期不是在 Component 中定义的,这一点在后续的章节会详细讲解。

② PureComponent

和 Component 是基本一致的,只是比 Component 多了一个属性

  1. pureComponentPrototype.isPureReactComponent = true;

以下是 PureComponent 的数据结构

  1. {
  2. props: {},
  3. context: {},
  4. refs: {},
  5. updater: {...},
  6. state: null,
  7. __proto__: {
  8. constructor: class Demo
  9. render: f
  10. __proto__: {
  11. constructor: f,
  12. setState: f,
  13. forceUpdate: f,
  14. isReactComponent: {},
  15. isPureReactComponent: true //多了这个
  16. }
  17. }
  18. }

③ 多出的[isPureReactComponent]有什么用?

  1. if (ctor.prototype && ctor.prototype.isPureReactComponent) {
  2. return (
  3. !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
  4. );
  5. }

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

顺便说一下:React中对比一个ClassComponent是否需要更新,只有两个地方。一是看有没有shouldComponentUpdate方法,二就是这里的PureComponent判断

Ⅳ-createContext

createContext 是官方定稿的 context 方案,在这之前我们一直在用的老的 context API 都是 React 不推荐的 API,官方在17大版本会把老 API 去除。

Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。下面的例子展示了在顶层组件通过 [Provider] 的方式提供了一个值,在下层组件可以通过 [Consumer] 拿到,中间不需要任何的 props 传参。

```jsx const { Provider, Consumer } = React.createContext(‘default’);

class Top extends React.Component { state = { contextValue: ‘123’ }; render() { return ( {this.props.children} ) } }

class Bottom extends React.Component { render() { return ( {value =>

contextValue: {value}

} )
} }

export default () => ( )

  1. > 查看React.createContext的源码,我摘出其中的重要代码:
  2. > ```javascript
  3. // react\src\ReactContext.js
  4. export function createContext<T>(
  5. defaultValue: T,
  6. calculateChangedBits: ?(a: T, b: T) => number,
  7. ): ReactContext<T> {
  8. const context: ReactContext<T> = {
  9. $$typeof: REACT_CONTEXT_TYPE,
  10. _calculateChangedBits: calculateChangedBits,
  11. //作为支持多个并发渲染器的解决方案,我们将其分类
  12. //一些渲染器是主要的,另一些是次要的。 我们只希望
  13. //最多有两个并发渲染器:React Native (primary)和
  14. //面料(二级); React DOM(主要)和React ART(次要)。
  15. //二级渲染器将它们的上下文值存储在单独的字段中。
  16. _currentValue: defaultValue,
  17. _currentValue2: defaultValue,
  18. //用于跟踪当前有多少并发渲染此上下文
  19. //在单个渲染器中支持。 例如并行服务器呈现。
  20. _threadCount: 0,
  21. // These are circular
  22. Provider: (null: any),
  23. Consumer: (null: any),
  24. };
  25. context.Provider = {
  26. $$typeof: REACT_PROVIDER_TYPE,
  27. _context: context,
  28. };
  29. context.Consumer = context;
  30. return context;
  31. }

createContext接收的是一个 defaultValue ,还有一个是 calculateChangedBits 。这是一个方法,这个方法接受 newValue 与 oldValue 的函数,返回值作为 changedBits,在 Provider 中,当 changedBits = 0,将不再触发更新。

方法里面声明了一个 context 对象,有一个 typeof 属性,需要注意的是,这个 [typeof]ReactElement的 [$$typeof] 是不一样的

下面列出了两个$$typeof的不同之处。

  1. {
  2. $$typeof: REACT_ELEMENT_TYPE // 其实是一个Symbol类型的标志
  3. type: {
  4. $$typeof: REACT_PROVIDER_TYPE // 其实是一个Symbol类型的标志
  5. _currentValue: "default",
  6. ...
  7. }
  8. }

_currentValue , _currentValue2 这两个属性是一样的,只是用到的地方不一样。 _currentValue 这个 value 是用来记录 Provider 里面的这个 value 。 他有变化的情况下就会更新到这个 _currentValue 。是用来记录最新的 context 的值的。

context.Provider 有个属性 _context ,这个属性会指向这个 context。context.Consumer = context。 也就是说 Consumer 是等于自己的

Ⅴ-forwardRef

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

在低版本使用string ref的时候,ref 是不能加在 Component 上的。原因也很简单,ref 这个字段会被单独处理,不会传递到子组件上(详见ReactElement部分)。React16之后,通过 forwardRef 就能实现父组件向子组件自动传递引用 ref。这种需求通常发生在需要处理焦点,动画,或者是集成第三方 Dom 库的时候。下面列举了一个处理输入框焦点的例子。

```jsx import React, { Component, createRef, forwardRef } from ‘react’; //forwardRef使用方式举例 const FocusInput = forwardRef((props, ref) => ( ));

class ForwardRef extends Component { constructor(props) { super(props); this.ref = createRef(); }

componentDidMount() { const { current } = this.ref; current.focus(); }

render() { return (

forward ref

); } } export default ForwardRef;

  1. > 查看 forwardRef 源码。
  2. > ```javascript
  3. // react\src\forwardRef.js
  4. export default function forwardRef<Props, ElementType: React$ElementType>(
  5. //render函数作为参数-->即包装的FunctionComponent
  6. render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
  7. ) {
  8. return {
  9. $$typeof: REACT_FORWARD_REF_TYPE,
  10. render,
  11. };
  12. }
  1. 被forwardRef包裹后,组件内部的$$typeof是REACT_FORWARD_REF_TYPE
  2. render即包装的FunctionComponent,ClassComponent是不用forwardRef的

Ⅵ-四个类型

  1. Fragment: REACT_FRAGMENT_TYPE
  2. Profiler: REACT_PROFILER_TYPE
  3. StrictMode: REACT_STRICT_MODE_TYPE
  4. Suspense: REACT_SUSPENSE_TYPE

这四个都是React提供的组件,但他们其实都只是占位符,都是一个Symbol,在React实际检测到他们的时候会做一些特殊的处理。

① Fragment

Fragment 是我们使用最多的,React 中一个常见模式是为一个组件返回多个元素。Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点。 <> 是 的语法糖。

  1. render() {
  2. return (
  3. <> //<Fragment></Fragment> 等同
  4. <ChildA />
  5. <ChildB />
  6. </>
  7. );
  8. }

② Profiler

Profiler是用来测量渲染性能的,几乎不用。

③ StrictMode

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。

```jsx import React from ‘react’;

function ExampleApplication() { return (

); }

  1. > 在上述的示例中,不会对 Header Footer 组件运行严格模式检查。但是,`ComponentOne` `ComponentTwo` 以及`它们的所有后代元素`都将进行检查。
  2. <a name="f68a6dd8"></a>
  3. #### ④ Suspense
  4. > ```jsx
  5. const OtherComponent = React.lazy(() => import('./OtherComponent'));
  6. function MyComponent() {
  7. return (
  8. <div>
  9. <Suspense fallback={<div>Loading...</div>}>
  10. <OtherComponent />
  11. </Suspense>
  12. </div>
  13. );
  14. }

OtherComponent是通过懒加载加载进来的,所以渲染页面的时候可能会有延迟,但使用了Suspense之后,可优化交互。 在外面使用Suspense标签,并在fallback中声明OtherComponent加载完成前做的事,即可优化整个页面的交互

fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。

Ⅶ-四个Element API

① createElement &

createElement可谓是React中最重要的API了,他是用来创建ReactElement的,但是很多同学却从没见过也没用过,这是为啥呢?因为你用了JSX,JSX并不是标准的js,所以要经过编译才能变成可运行的js,而编译之后,createElement就出现了:

```javascript // jsx

content

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

  1. <a name="89dde429"></a>
  2. #### ② cloneElement
  3. > `cloneElement`就很明显了,是用来克隆一个`ReactElement`的
  4. <a name="48967ac2"></a>
  5. #### ③ createFactory
  6. > `createFactory`是用来创建专门用来创建某一类`ReactElement`的工厂的
  7. > ```javascript
  8. export function createFactory(type) {
  9. const factory = createElement.bind(null, type);
  10. factory.type = type;
  11. return factory;
  12. }

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

④ isValidElement

isValidElement顾名思义就是用来验证是否是一个ReactElement的,基本也用不太到

4、React Children

此处为什么不放在API梳理中而是单独拿出来讲? [同学们可以忽略不看此部分]

一方面平时不怎么用,另一方面跟数组处理功能差不多,不深究实现是比较容易理解的.但是观阅许多相关资料后,觉得这个还是可以记录一下

以[React.Children.map]进行分析

mapforEach的最大区别就是有没有return result

Ⅰ- 使用方式

首先看下这个 API 的用法

```jsx import React from ‘react’

function ChildrenDemo(props) { return React.Children.map(props.children, c => [c, c]) }

export default () => (

Tom and Jerry
)

  1. > 界面上显示
  2. > ```javascript
  3. Tom and Jerry
  4. Tom and Jerry

如果将上述的代码稍微调整

  1. function ChildrenDemo(props) {
  2. return React.Children.map(props.children, c => [c, [c, c]])
  3. }

有意思的事情发生了,界面上显示

  1. Tom and Jerry
  2. Tom and Jerry
  3. Tom and Jerry

Ⅱ- 源码分析

接下来从代码的角度分析,为什么出现这种情况,从React入口文件开始

```javascript // react\src\React.js import {forEach, map, count, toArray, only} from ‘./ReactChildren’;

const React = { Children: { map, forEach, count, toArray, only, },

}

  1. > 可以看出,React Clildren 的所有 API 来自 ReactChildren.js 文件。
  2. > ```javascript
  3. // react\src\ReactChildren.js
  4. export {
  5. forEachChildren as forEach,
  6. mapChildren as map,
  7. countChildren as count,
  8. onlyChild as only,
  9. toArray,
  10. }

map 只是个别名,真正的函数是 mapChildren

```javascript // react\src\ReactChildren.js function mapChildren(children, func, context) { if (children == null) { return children; } const result = []; mapIntoWithKeyPrefixInternal(children, result, null, func, context); // map和forEach的最大区别就是有没有return result。 return result; }

function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { let escapedPrefix = ‘’;

// 用来生成key的 if (prefix != null) { escapedPrefix = escapeUserProvidedKey(prefix) + ‘/‘; } const traverseContext = getPooledTraverseContext( array, escapedPrefix, func, context, ); traverseAllChildren(children, mapSingleChildIntoContext, traverseContext); releaseTraverseContext(traverseContext); }

  1. > `map``forEach`的最大区别就是有没有`return result`
  2. <a name="c4486034"></a>
  3. #### ① 分析 getPooledTraverseContext 和 releaseTraverseContext
  4. > getPooledTraverseContext 就是从 pool 里面找一个对象,releaseTraverseContext 会把当前的 context 对象清空然后放回到 pool 中。
  5. > ```jsx
  6. // react\src\ReactChildren.js
  7. const POOL_SIZE = 10;
  8. const traverseContextPool = [];
  9. function getPooledTraverseContext(
  10. mapResult,
  11. keyPrefix,
  12. mapFunction,
  13. mapContext,
  14. ) {
  15. if (traverseContextPool.length) {
  16. const traverseContext = traverseContextPool.pop();
  17. // set attrs
  18. return traverseContext;
  19. } else {
  20. return {
  21. result: mapResult,
  22. keyPrefix: keyPrefix,
  23. func: mapFunction,
  24. context: mapContext,
  25. count: 0,
  26. };
  27. }
  28. }
  29. function releaseTraverseContext(traverseContext) {
  30. // clear attrs
  31. if (traverseContextPool.length < POOL_SIZE) {
  32. traverseContextPool.push(traverseContext);
  33. }
  34. }

这么做主要是为了节省对象重复创建带来的性能消耗,React 中后续还会有一些这样的管理对象的方式。

那么按照这个流程来看,是不是pool永远都只有一个值呢,毕竟推出之后操作完了就推入了,这么循环着。答案肯定是否的,这就要讲到React.Children.map的一个特性了,那就是对每个节点的map返回的如果是数组,那么还会继续展开,这是一个递归的过程。接下去我们就来分析traverseAllChildren

② 分析traverseAllChildren 方法

traverseContext 对象封装完毕后,调用 traverseAllChildren 方法

```javascript // react\src\ReactChildren.js function traverseAllChildren(children, callback, traverseContext) { if (children == null) { return 0; }

return traverseAllChildrenImpl(children, ‘’, callback, traverseContext); }

  1. > 直接调用 traverseAllChildrenImpl 方法,`下方代码是连着上面的,只是拆出来更清晰`
  2. > ```javascript
  3. // react\src\ReactChildren.js
  4. // children: ReactElement
  5. // nameSoFar: ''
  6. // callback: 是个函数 mapSingleChildIntoContext,在mapChildren 函数中传入
  7. // traverseContext: { result: [], keyPrefix: "", func: c => [c, [c, c]], context: undefined, count: 0}
  8. function traverseAllChildrenImpl(
  9. children,
  10. nameSoFar,
  11. callback,
  12. traverseContext,
  13. ) {
  14. // children一般是一个React Element,或者是多个React Element
  15. const type = typeof children;
  16. // 判断children的合理性
  17. if (type === 'undefined' || type === 'boolean') {
  18. children = null
  19. }
  20. let invokeCallback = false
  21. // 对于单个节点,invokeCallback = true
  22. if (children === null) {
  23. invokeCallback = true
  24. } else {
  25. switch (type) {
  26. case 'string':
  27. case 'number':
  28. invokeCallback = true
  29. break
  30. case 'object':
  31. switch (children.$$typeof) {
  32. case REACT_ELEMENT_TYPE:
  33. case REACT_PORTAL_TYPE:
  34. invokeCallback = true
  35. }
  36. }
  37. }
  38. if (invokeCallback) {
  39. // callback 指的是 mapSingleChildIntoContext
  40. callback(
  41. traverseContext,
  42. children,
  43. //此处是设置Key的代码
  44. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
  45. )
  46. return 1
  47. }
  48. let child;
  49. let nextName;
  50. let subtreeCount = 0; // Count of children found in the current subtree.
  51. const nextNamePrefix =
  52. nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
  53. // 对于本例,第一次进入这个函数 clildren 是两个 span,是一个array
  54. if (Array.isArray(children)) {
  55. for (let i = 0; i < children.length; i++) {
  56. child = children[i];
  57. nextName = nextNamePrefix + getComponentKey(child, i);
  58. // 递归调用 traverseAllChildrenImpl
  59. subtreeCount += traverseAllChildrenImpl(
  60. child,
  61. nextName,
  62. callback,
  63. traverseContext,
  64. );
  65. }
  66. } else {
  67. // 针对的是那些不是Array类型,但是有迭代器,能遍历的情况,正常代码不会这样,可以忽略
  68. const iteratorFn = getIteratorFn(children);
  69. if (typeof iteratorFn === 'function') {
  70. const iterator = iteratorFn.call(children);
  71. let step;
  72. let ii = 0;
  73. while (!(step = iterator.next()).done) {
  74. child = step.value;
  75. nextName = nextNamePrefix + getComponentKey(child, ii++);
  76. subtreeCount += traverseAllChildrenImpl(
  77. child,
  78. nextName,
  79. callback,
  80. traverseContext,
  81. );
  82. }
  83. } else if (type === 'object') {
  84. let addendum = '';
  85. const childrenString = '' + children;
  86. invariant(
  87. false,
  88. 'Objects are not valid as a React child (found: %s).%s',
  89. childrenString === '[object Object]'
  90. ? 'object with keys {' + Object.keys(children).join(', ') + '}'
  91. : childrenString,
  92. addendum,
  93. );
  94. }
  95. }
  96. return subtreeCount;
  97. }

traverseAllChildrenImpl 函数做的事情比较简单,children如果是可循环的,遍历children中的每个元素,递归调用本身,直到是一个节点的情况, 然后调用callback,也就是 mapSingleChildIntoContext。

```javascript // react\src\ReactChildren.js function mapSingleChildIntoContext(bookKeeping, child, childKey) { const {result, keyPrefix, func, context} = bookKeeping;

// child 调用 我们传入的函数 c => [c, [c, c]] let mappedChild = func.call(context, child, bookKeeping.count++);

// 如果 返回的值依然是个数组,递归调用mapIntoWithKeyPrefixInternal if (Array.isArray(mappedChild)) { // 注意 c => c mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); } else if (mappedChild != null) { // 如果返回的值是个合格的Element,将结果放入result中 if (isValidElement(mappedChild)) { mappedChild = cloneAndReplaceKey( mappedChild, // Keep both the (mapped) and old keys if they differ, just as // traverseAllChildren used to do for objects as children keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key) ? escapeUserProvidedKey(mappedChild.key) + ‘/‘ : ‘’) + childKey, ); } result.push(mappedChild); } }

  1. > mapSingleChildIntoContext 这个方法其实就是调用 React.Children.map(children, callback) 这里的callback,就是我们传入的第二个参数,并得到map之后的结果。
  2. > - 如果`map`之后的节点还是一个数组,那么再次进入`mapIntoWithKeyPrefixInternal`,那么这个时候我们就会再次从`pool`里面去`context`了,而`pool`的意义大概也就是在这里了,如果循环嵌套多了,可以减少很多对象创建和`gc`的损耗。
  3. > - 而如果不是数组并且是一个合规的ReactElement,就触达顶点了,替换一下key就推入result了。
  4. > - React 将重复对象放在Pool中是因为对Children的处理一般在render里面,所以会比较频繁,所以设置一个pool减少声明和gc的开销。
  5. <a name="a46a6662"></a>
  6. #### ③ React 这么实现的目的
  7. > 1. 拆分`map`出来的数组
  8. > 2. 因为对`Children`的处理一般在`render`里面,所以会比较频繁,所以设置一个`pool`减少声明和`gc`的开销
  9. <a name="2b719acf"></a>
  10. ### Ⅲ-Key值的设置
  11. > 将原先的一个children映射为多个children的过程中,涉及到了一些key值的变换
  12. > ```javascript
  13. //源码
  14. function traverseAllChildrenImpl() {
  15. ...
  16. if (invokeCallback) {
  17. callback(
  18. traverseContext,
  19. children,
  20. //如果它是唯一的子元素,那么就把它当作包装在数组中
  21. //如果孩子的数量增长,它是一致的。
  22. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
  23. );
  24. return 1;
  25. }
  26. ...
  27. const nextNamePrefix =
  28. nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
  29. if (Array.isArray(children)) {
  30. for (let i = 0; i < children.length; i++) {
  31. child = children[i];
  32. nextName = nextNamePrefix + getComponentKey(child, i);
  33. subtreeCount += traverseAllChildrenImpl(
  34. child,
  35. nextName,
  36. callback,
  37. traverseContext,
  38. );
  39. }
  40. }
  41. ...
  42. }

举例来说明key的生成过程

  1. // React.Children.map(props.children, c => c)
  2. () => (
  3. <div >Tom</div>
  4. <div >Jerry</div>
  5. )

如果 children 是一个数组,那么首先会计算一个 nextNamePrefix,计算的规则如下

  1. // const SEPARATOR = '.';
  2. // const SUBSEPARATOR = ':';
  3. const nextNamePrefix =
  4. nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

nameSoFar === ‘’ 成立, nextNamePrefix 是 SEPARATOR ,也就是 “.”。在遍历数组的过程中

  1. nextName = nextNamePrefix + getComponentKey(child, i);

getComponentKey 会获得元素上的key值,如果没有key值,那么就是 i, i代表数组中的index。对于一个对象调用callback的过程中,key的规则是

  1. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar

由于此时nameSoFar不为空串, Tom 的 key是 “.0”, Jerry 的 key 是 “.1”。

如果 c => [ c, c ],即映射出的是数组,那么在 mapSingleChildIntoContext 函数中 会走到这个分支

  1. if (Array.isArray(mappedChild)) {
  2. mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
  3. }

此时会重新调用 mapIntoWithKeyPrefixInternal,也就是

  1. if (prefix != null) {
  2. escapedPrefix = escapeUserProvidedKey(prefix) + '/';
  3. }

执行完毕后,key后面多加了 ‘/’ 。继续执行 traverseAllChildrenImpl,nameSoFar === ‘’ 成立, nextNamePrefix 是 SEPARATOR ,也就是 ‘.’, key后面多加了 ‘.’。再次执行

  1. nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar

最终生成的4个key分别是

  1. .0/.0
  2. .0/.1
  3. .1/.0
  4. .1/.1

如果 c => [ c, [c, c] ],那么

  1. const nextNamePrefix =
  2. nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

会被循环调用,第二次进入循环的时候,nameSoFar === ‘’这个条件不满足,key后面会加一个 ‘SUBSEPARATOR’,也就是 ‘:’,其余流程不变。

最终生成的6个key分别是

  1. .0/.0
  2. .0/.1:0
  3. .0/.1:1
  4. .1/.0
  5. .1/.1:0
  6. .1/.1:1

还需要注意的是,如果component设置了key,会使用component上的key,不使用index,此时会如果两个元素的key相同,react会给出警告。

5、React的数据结构

了解即可,可跳过

Ⅰ-FiberRoot

```javascript type BaseFiberRootProperties = {| // root节点,render方法接收的第二个参数 containerInfo: any, // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到 pendingChildren: any, // 当前应用对应的Fiber对象,是Root Fiber current: Fiber,

// 一下的优先级是用来区分 // 1) 没有提交(committed)的任务 // 2) 没有提交的挂起任务 // 3) 没有提交的可能被挂起的任务 // 我们选择不追踪每个单独的阻塞登记,为了兼顾性能 // The earliest and latest priority levels that are suspended from committing. // 最老和新的在提交的时候被挂起的任务 earliestSuspendedTime: ExpirationTime, latestSuspendedTime: ExpirationTime, // The earliest and latest priority levels that are not known to be suspended. // 最老和最新的不确定是否会挂起的优先级(所有任务进来一开始都是这个状态) earliestPendingTime: ExpirationTime, latestPendingTime: ExpirationTime, // The latest priority level that was pinged by a resolved promise and can // be retried. // 最新的通过一个promise被reslove并且可以重新尝试的优先级 latestPingedTime: ExpirationTime,

// 如果有错误被抛出并且没有更多的更新存在,我们尝试在处理错误前同步重新从头渲染 // 在renderRoot出现无法处理的错误时会被设置为true didError: boolean,

// 正在等待提交的任务的expirationTime pendingCommitExpirationTime: ExpirationTime, // 已经完成的任务的FiberRoot对象,如果你只有一个Root,那他永远只可能是这个Root对应的Fiber,或者是null // 在commit阶段只会处理这个值对应的任务 finishedWork: Fiber | null, // 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout timeoutHandle: TimeoutHandle | NoTimeout, // 顶层context对象,只有主动调用renderSubtreeIntoContainer时才会有用 context: Object | null, pendingContext: Object | null, // 用来确定第一次渲染的时候是否需要融合 +hydrate: boolean, // 当前root上剩余的过期时间 // TODO: 提到renderer里面区处理 nextExpirationTimeToWorkOn: ExpirationTime, // 当前更新对应的过期时间 expirationTime: ExpirationTime, // List of top-level batches. This list indicates whether a commit should be // deferred. Also contains completion callbacks. // TODO: Lift this into the renderer // 顶层批次(批处理任务?)这个变量指明一个commit是否应该被推迟 // 同时包括完成之后的回调 // 貌似用在测试的时候? firstBatch: Batch | null, // root之间关联的链表结构 nextScheduledRoot: FiberRoot | null, |};

  1. <a name="54d95f22"></a>
  2. ### Ⅱ-Fiber
  3. > ```javascript
  4. // Fiber对应一个组件需要被处理或者已经处理了,一个组件可以有一个或者多个Fiber
  5. type Fiber = {|
  6. // 标记不同的组件类型
  7. tag: WorkTag,
  8. // ReactElement里面的key
  9. key: null | string,
  10. // ReactElement.type,也就是我们调用`createElement`的第一个参数
  11. elementType: any,
  12. // The resolved function/class/ associated with this fiber.
  13. // 异步组件resolved之后返回的内容,一般是`function`或者`class`
  14. type: any,
  15. // The local state associated with this fiber.
  16. // 跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)
  17. stateNode: any,
  18. // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
  19. return: Fiber | null,
  20. // 单链表树结构
  21. // 指向自己的第一个子节点
  22. child: Fiber | null,
  23. // 指向自己的兄弟结构
  24. // 兄弟节点的return指向同一个父节点
  25. sibling: Fiber | null,
  26. index: number,
  27. // ref属性
  28. ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
  29. // 新的变动带来的新的props
  30. pendingProps: any,
  31. // 上一次渲染完成之后的props
  32. memoizedProps: any,
  33. // 该Fiber对应的组件产生的Update会存放在这个队列里面
  34. updateQueue: UpdateQueue<any> | null,
  35. // 上一次渲染的时候的state
  36. memoizedState: any,
  37. // 一个列表,存放这个Fiber依赖的context
  38. firstContextDependency: ContextDependency<mixed> | null,
  39. // 用来描述当前Fiber和他子树的`Bitfield`
  40. // 共存的模式表示这个子树是否默认是异步渲染的
  41. // Fiber被创建的时候他会继承父Fiber
  42. // 其他的标识也可以在创建的时候被设置
  43. // 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
  44. mode: TypeOfMode,
  45. // Effect
  46. // 用来记录Side Effect
  47. effectTag: SideEffectTag,
  48. // 单链表用来快速查找下一个side effect
  49. nextEffect: Fiber | null,
  50. // 子树中第一个side effect
  51. firstEffect: Fiber | null,
  52. // 子树中最后一个side effect
  53. lastEffect: Fiber | null,
  54. // 代表任务在未来的哪个时间点应该被完成
  55. // 不包括他的子树产生的任务
  56. expirationTime: ExpirationTime,
  57. // 快速确定子树中是否有不在等待的变化
  58. childExpirationTime: ExpirationTime,
  59. // 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
  60. // 我们称他为`current <==> workInProgress`
  61. // 在渲染完成之后他们会交换位置
  62. alternate: Fiber | null,
  63. // 下面是调试相关的,收集每个Fiber和子树渲染时间的
  64. actualDuration?: number,
  65. // If the Fiber is currently active in the "render" phase,
  66. // This marks the time at which the work began.
  67. // This field is only set when the enableProfilerTimer flag is enabled.
  68. actualStartTime?: number,
  69. // Duration of the most recent render time for this Fiber.
  70. // This value is not updated when we bailout for memoization purposes.
  71. // This field is only set when the enableProfilerTimer flag is enabled.
  72. selfBaseDuration?: number,
  73. // Sum of base times for all descedents of this Fiber.
  74. // This value bubbles up during the "complete" phase.
  75. // This field is only set when the enableProfilerTimer flag is enabled.
  76. treeBaseDuration?: number,
  77. // Conceptual aliases
  78. // workInProgress : Fiber -> alternate The alternate used for reuse happens
  79. // to be the same as work in progress.
  80. // __DEV__ only
  81. _debugID?: number,
  82. _debugSource?: Source | null,
  83. _debugOwner?: Fiber | null,
  84. _debugIsCurrentlyTiming?: boolean,
  85. |};

Ⅲ-sideEffects

```javascript /**

  • Copyright (c) Facebook, Inc. and its affiliates. *
  • This source code is licensed under the MIT license found in the
  • LICENSE file in the root directory of this source tree. *
  • @flow */

export type SideEffectTag = number;

// Don’t change these two values. They’re used by React Dev Tools. export const NoEffect = / / 0b00000000000; export const PerformedWork = / / 0b00000000001;

// You can change the rest (and add more). export const Placement = / / 0b00000000010; export const Update = / / 0b00000000100; export const PlacementAndUpdate = / / 0b00000000110; export const Deletion = / / 0b00000001000; export const ContentReset = / / 0b00000010000; export const Callback = / / 0b00000100000; export const DidCapture = / / 0b00001000000; export const Ref = / / 0b00010000000; export const Snapshot = / / 0b00100000000;

// Update & Callback & Ref & Snapshot export const LifecycleEffectMask = / / 0b00110100100;

// Union of all host effects export const HostEffectMask = / / 0b00111111111;

export const Incomplete = / / 0b01000000000; export const ShouldCapture = / / 0b10000000000;

  1. <a name="87517471"></a>
  2. ### Ⅳ-ReactWorkTag
  3. > ```javascript
  4. export const FunctionComponent = 0;
  5. export const ClassComponent = 1;
  6. export const IndeterminateComponent = 2; // Before we know whether it is function or class
  7. export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
  8. export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
  9. export const HostComponent = 5;
  10. export const HostText = 6;
  11. export const Fragment = 7;
  12. export const Mode = 8;
  13. export const ContextConsumer = 9;
  14. export const ContextProvider = 10;
  15. export const ForwardRef = 11;
  16. export const Profiler = 12;
  17. export const SuspenseComponent = 13;
  18. export const MemoComponent = 14;
  19. export const SimpleMemoComponent = 15;
  20. export const LazyComponent = 16;
  21. export const IncompleteClassComponent = 17;

Ⅴ-Update & UpdateQueue

```javascript export type Update = { // 更新的过期时间 expirationTime: ExpirationTime,

// export const UpdateState = 0; // export const ReplaceState = 1; // export const ForceUpdate = 2; // export const CaptureUpdate = 3; // 指定更新的类型,值为以上几种 tag: 0 | 1 | 2 | 3, // 更新内容,比如setState接收的第一个参数 payload: any, // 对应的回调,setStaterender都有 callback: (() => mixed) | null,

// 指向下一个更新 next: Update | null, // 指向下一个side effect nextEffect: Update | null, };

export type UpdateQueue = { // 每次操作完更新之后的state baseState: State,

// 队列中的第一个Update firstUpdate: Update | null, // 队列中的最后一个Update lastUpdate: Update | null,

// 第一个捕获类型的Update firstCapturedUpdate: Update | null, // 最后一个捕获类型的Update lastCapturedUpdate: Update | null,

// 第一个side effect firstEffect: Update | null, // 最后一个side effect lastEffect: Update | null,

// 第一个和最后一个捕获产生的side effect firstCapturedEffect: Update | null, lastCapturedEffect: Update | null, };

  1. <a name="9f156b78"></a>
  2. ## 6、对虚拟DOM的理解
  3. <a name="2b282ce8"></a>
  4. ### Ⅰ-什么是虚拟DOM
  5. > -
  6. 在React、Vue还没有出现的时候,我们要操作页面上的元素,需要`先找到那个元素`,然后`再进行修改样式、内容或者结构等`-->这很明显性能特别差,也会特别消耗资源!
  7. > -
  8. 于是就有人会这样思考:如果在操作DOM之前就知道这一次数据更新期望改变的[`节点和`]怎么修改的话,那么这很明显可以提升页面渲染效率
  9. > -
  10. > 举个栗子:渲染列表时比如有100条数据,我删除其中一条,那么我只是在`虚拟DOM数据`上删除此条数据,然后剩余的经过diff算法对比,其实只是删除了一条,其余的只是调整了在页面上的位置没有重新渲染;直接操作dom就会导致数据重绘(临时举例不够贴切勿怪)
  11. > -
  12. 于是[`虚拟DOM`]出现了,`它其实是一个数据结构`,是针对当前DOM构建的一个数据结构
  13. > -
  14. 以下是一个React的虚拟DOM结构
  15. > ![](React%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E7%AC%94%E8%AE%B0%E4%B8%AD%E7%9A%84%E5%9B%BE%E7%89%87/image-20210715180450728.png#alt=image-20210715180450728)
  16. <a name="fd7a6bd4"></a>
  17. ### Ⅱ-为什么需要虚拟DOM
  18. > 其实在上面说明[[什么是虚拟DOM](#%E2%85%A0-%E4%BB%80%E4%B9%88%E6%98%AF%E8%99%9A%E6%8B%9FDOM)]的时候就已经大概说明了为什么需要虚拟DOM了.因为`一个新事物只有被需要、能解决某一痛点的时候才会被创造`
  19. > -
  20. 无脑的渲染是否真的必要?
  21. > -
  22. > 很明显不需要.但是在没有虚拟DOM之前,我们能依靠的只有直接操作DOM,尽管我们知道操作DOM而产生的`重绘`会对页面性能产生巨大影响
  23. > -
  24. 当一个项目大到一种境界的时候,如果还是用查找元素然后进行修改的话,对代码的维护难度会呈现指数级飙升
  25. > -
  26. 它是Diff算法的技术,也是数据驱动UI的前提
  27. > 个人理解的虚拟DOM大致流程图(公司前辈讲解的)
  28. > ![](React深入学习与源码解析笔记中的图片/image-20210715182038527.png#alt=image-20210715182038527)
  29. <a name="e9b44264"></a>
  30. ### Ⅲ- 误区:虚拟DOM比直接操作DOM快?
  31. > - 很多人有这个误区,甚至我在之前很长的一段时间中都是这样认为的.其实不然,通过上面的流程图其实可以很好理解:`直接操作DOM是最快的`.
  32. > - 如果通过虚拟DOM计算出的更新策略是需要重绘10次,而直接操作DOM的次数也是重绘10次,那么直接操作应该是更快的,因为它省去了中间虚拟DOM构建、Diff算法、制定更新策略等;
  33. > - 当然,正常情况下针对一个[`state`(状态)]变化后期望产生结果得出的`更新次数是远小于直接操作DOM`的,预知更新策略比直接无脑操作虽然会花费一部分内存,但是直接操作减少了,性能肯定会更好,做出来的应用的会更加健壮
  34. <a name="a72ee0e9"></a>
  35. #### 无图无真相
  36. > 网络上有人做了一个操作更新组件各种样式1000次的时间点对比(设定重绘次数一致):
  37. > ![](React%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E7%AC%94%E8%AE%B0%E4%B8%AD%E7%9A%84%E5%9B%BE%E7%89%87/image-20210715184238480.png#alt=image-20210715184238480)
  38. > 可以看出,直接操作DOM是最快的!`但是React需要的渲染时间可能是0`,下限更低,也正是因为计算出来某些[`state`(状态)]不需要更新DOM
  39. <a name="09ff67b7"></a>
  40. # 二、创建更新
  41. > react创建更新的方式有三种
  42. > - ReactDOM.render
  43. > - setState
  44. > - forceUpdate
  45. <a name="fe1b39fe"></a>
  46. ## 1、ReactDOM.render
  47. <a name="d964d63d"></a>
  48. ### Ⅰ-使用概述
  49. > ```jsx
  50. // index.js
  51. import React from 'react';
  52. import ReactDOM from 'react-dom';
  53. import App from './App';
  54. ReactDOM.render(<App />, document.getElementById('root'));

ReactDOM.render 是 React 程序的起点。ReactDom 中包含了 createPortal,findDOMNode, render 等方法,这里需要注意的是 hydraterender 方法,这两个方法其实调用的是一个函数,只是一个参数不同,hydrate 一般用于服务端渲染,它会复用服务端返回的Html结构。我们这次只分析 render,hydrate后续会单独分析。

Ⅱ-ReactDOM源码示例

[unstable_createRoot] 和 [unstable_createSyncRoot] 这两个接口需要注意下,特别是 createRoot,其实就是 16.9 之前的 [Concurrent Mode] 的替代,会启用 React 的并行模型。

创建ReactRoot,并且根据情况调用root.legacy_renderSubtreeIntoContainer或者root.render,前者是遗留的 API 将来应该会删除,根据ReactDOM.render的调用情况也可以发现parentComponent是写死的null

```javascript // react-dom\src\client\ReactDOM.js const ReactDOM: Object = { hydrate(element: React$Node, container: DOMContainer, callback: ?Function) { // TODO: throw or warn if we couldn’t hydrate? return legacyRenderSubtreeIntoContainer( null, element, container, true, callback, ); },

render( element: React$Element, container: DOMContainer, callback: ?Function, ) { return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); },

unstable_createRoot: createRoot, unstable_createSyncRoot: createSyncRoot, }

  1. <a name="c898b3e6"></a>
  2. ### Ⅲ-render源码解析
  3. > render 接受三个参数,第一个参数是`ReactElement`,第二个参数为`组件所要挂载的DOM节点`,第三个参数为`回调函数`。
  4. > ```jsx
  5. render(
  6. element: React$Element<any>,
  7. container: DOMContainer, //组件索要挂在的DOM节点
  8. callback: ?Function, //回调函数
  9. ) {
  10. //将继承来的render渲染成容器
  11. return legacyRenderSubtreeIntoContainer(
  12. null,
  13. element,
  14. container,
  15. false,
  16. callback,
  17. );
  18. },

① legacyRenderSubtreeIntoContainer

[legacyRenderSubtreeIntoContainer] 函数第一个入参 parentComponent 是写死的 null,函数首先生成了一个 root 对象, 调用的方法是 [legacyCreateRootFromDOMContainer]。

首先会创建ReactRoot对象,然后调用他的render方法

```jsx // react-dom\src\client\ReactDOM.js function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component, // null children: ReactNodeList, // ReactElement container: DOMContainer, // dom节点 forceHydrate: boolean, callback: ?Function, ) { // 第一次 container 上没有 _reactRootContainer 属性,所以初次渲染为 null let root: _ReactSyncRoot = (container._reactRootContainer: any); let fiberRoot; if (!root) { // 生成一个 root 节点 ==根据DOM容器生成一个 根节点 root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot;

// 处理回调函数 if (typeof callback === ‘function’) { const originalCallback = callback; callback = function() { // 其实就是 root.current.child.stateNode const instance = getPublicRootInstance(fiberRoot); //以instance的身份调用回调函数 originalCallback.call(instance); }; } //DOMRenderer.unbatchedUpdates制定不使用batchedUpdates,因为这是初次渲染,需要尽快完成。
// 不会进行批量策略的更新 unbatchedUpdates(() => { updateContainer(children, fiberRoot, parentComponent, callback); }); } else { // root已经存在的情况,会复用之前生成的root,暂时不考虑这种情况 } return getPublicRootInstance(fiberRoot); }

  1. > 下一步运行`legacyCreateRootFromDOMContainer` -->[③ legacyCreateRootFromDOMContainer](#③ legacyCreateRootFromDOMContainer)
  2. > 接着解析初次渲染不会进入的[`updateContainer`]函数
  3. <a name="0b16f845"></a>
  4. #### ② updateContainer
  5. > 此处虽然初次渲染先不会进入[`updateContainer`]函数,但仍在此处给出部分解析,在[⑦ createFiber](#⑦ createFiber)创建完RootFiber后将会回到此处运行
  6. > 其中`DOMRenderer``react-reconciler/src/ReactFiberReconciler`,他的`updateContainer`如下在这里计算了一个时间,这个时间叫做`expirationTime`,顾名思义就是这次更新的 **超时时间**。
  7. > 关于时间是如何计算的 -->请看[expiration Time]部分
  8. > ```javascript
  9. // react-reconciler\src\ReactFiberReconciler
  10. export function updateContainer(
  11. element: ReactNodeList,
  12. container: OpaqueRoot,
  13. parentComponent: ?React$Component<any, any>,
  14. callback: ?Function,
  15. ): ExpirationTime {
  16. const current = container.current
  17. const currentTime = requestCurrentTime()
  18. // 计算超时时间
  19. const expirationTime = computeExpirationForFiber(currentTime, current)
  20. return updateContainerAtExpirationTime(
  21. element,
  22. container,
  23. parentComponent,
  24. expirationTime,
  25. callback,
  26. )
  27. }
  28. export function updateContainerAtExpirationTime(
  29. element: ReactNodeList,
  30. container: OpaqueRoot,
  31. parentComponent: ?React$Component<any, any>,
  32. expirationTime: ExpirationTime,
  33. callback: ?Function,
  34. ) {
  35. // TODO: If this is a nested container, this won't be the root.
  36. const current = container.current
  37. const context = getContextForSubtree(parentComponent)
  38. if (container.context === null) {
  39. container.context = context
  40. } else {
  41. container.pendingContext = context
  42. }
  43. //这个方法中开始了调度。
  44. return scheduleRootUpdate(current, element, expirationTime, callback)
  45. }

然后调用了updateContainerAtExpirationTime,在这个方法里调用了scheduleRootUpdate,这个方法中开始了调度。

首先要生成一个update,不管你是setState还是ReactDOM.render造成的 React 更新,都会生成一个叫update的对象,并且会赋值给Fiber.updateQueue

关于update请看此部分

scheduleWork

然后就是调用scheduleWork。注意到这里之前setStateReactDOM.render是不一样,但进入schedulerWork之后,就是任务调度的事情了,跟之前你是怎么调用的没有任何关系

```javascript // react-reconciler\src\ReactFiberReconciler function scheduleRootUpdate( current: Fiber, // RootFiber 对象 element: ReactNodeList,// ReactElement expirationTime: ExpirationTime,// 计算出的超时时间 suspenseConfig: null | SuspenseConfig, // null callback: ?Function, ) { //创建Updater const update = createUpdate(expirationTime) //设置payload为React Element update.payload = { element }

callback = callback === undefined ? null : callback if (callback !== null) { warningWithoutStack( typeof callback === ‘function’, ‘render(…): Expected the last optional callback argument to be a ‘ + ‘function. Instead received: %s.’, callback, ) //设置回调
update.callback = callback } //赋值给Fiber.updateQueue enqueueUpdate(current, update) //任务调度,此部分就于后面部分详解 scheduleWork(current, expirationTime) return expirationTime }

  1. > 下面看看`createUpdate`创建Updater
  2. <a name="createUpdate"></a>
  3. ##### createUpdate
  4. > 首先要做的就是创建一个 Updater React 中所有的更新都要创建 Updater
  5. > ```javascript
  6. // react-reconciler\src\ReactUpdateQueue.js
  7. export function createUpdate(
  8. expirationTime: ExpirationTime,
  9. suspenseConfig: null | SuspenseConfig,
  10. ): Update<*> {
  11. let update: Update<*> = {
  12. // 更新的过期时间
  13. expirationTime,
  14. suspenseConfig,
  15. // export const UpdateState = 0;
  16. // export const ReplaceState = 1;
  17. // export const ForceUpdate = 2;
  18. // export const CaptureUpdate = 3;
  19. // 指定更新的类型,值为以上几种
  20. tag: UpdateState,
  21. // 更新内容,比如`setState`接收的第一个参数
  22. payload: null,
  23. // 对应的回调,`setState`,`render`都有
  24. callback: null,
  25. // 指向下一个更新
  26. next: null,
  27. // 指向下一个`side effect`
  28. nextEffect: null,
  29. };
  30. return update;
  31. }

接下来会对 update 中的成员变量赋值,之后执行 enqueueUpdate

enqueueUpdate

```javascript // react-reconciler\src\ReactUpdateQueue.js export function enqueueUpdate(fiber: Fiber, update: Update) {

//alternate即workInProgress //fiber即current //current到alternate有一个映射关系 //所以要保证current和workInProgress的updateQueue是一致的 const alternate = fiber.alternate;

// current的队列 let queue1;

//alternate的队列 let queue2;

// 如果 alternate 为null,说明此时没有更新,没有workInProgress,只更新queue1 if (alternate === null) { // There’s only one fiber. queue1 = fiber.updateQueue; queue2 = null; if (queue1 === null) { // 初始化更新队列,赋值给fiber.updateQueue queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } } else { // There are two owners. // /如果alternate不为空,则取各自的更新队列 queue1 = fiber.updateQueue; queue2 = alternate.updateQueue; if (queue1 === null) { if (queue2 === null) { // Neither fiber has an update queue. Create new ones. queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); queue2 = alternate.updateQueue = createUpdateQueue( alternate.memoizedState, ); } else { // Only one fiber has an update queue. Clone to create a new one. // 如果queue2存在但queue1不存在的话,则根据queue2复制queue1 // 复制的时候 firstUpdate, lastUpdate 是共用的 queue1 = fiber.updateQueue = cloneUpdateQueue(queue2); } } else { if (queue2 === null) { // Only one fiber has an update queue. Clone to create a new one. queue2 = alternate.updateQueue = cloneUpdateQueue(queue1); } else { // Both owners have an update queue. } } }

// 经理上述步骤后,只可能是 queue2 为 null, queue1不可能为null if (queue2 === null || queue1 === queue2) { // There’s only a single queue. // 将update放入queue1中 appendUpdateToQueue(queue1, update); } else { // There are two queues. We need to append the update to both queues, // while accounting for the persistent structure of the list — we don’t // want the same update to be added multiple times. if (queue1.lastUpdate === null || queue2.lastUpdate === null) { // One of the queues is not empty. We must add the update to both queues. // react不想多次将同一个的update放入队列中 // 如果两个都是空队列,则添加update appendUpdateToQueue(queue1, update); appendUpdateToQueue(queue2, update);

// 如果两个都不是空队列,由于两个结构共享,所以只在queue1加入update // 在queue2中,将lastUpdate指向update } else { // Both queues are non-empty. The last update is the same in both lists, // because of structural sharing. So, only append to one of the lists. appendUpdateToQueue(queue1, update); // But we still need to update the lastUpdate pointer of queue2. queue2.lastUpdate = update; } } }

  1. > 这段代码看着复杂,但实际上做的事情很简单,首先,初始化fiber对象上的updateQueue,没有的话新建一个,然后将这个update放到updateQueue中,其中让人迷惑的点在于为什么有两个queue,以及alternate的作用,简单来讲,在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber 我们称他为`current <==> workInProgress`,在渲染完成之后他们会交换位置,这样就保证了更新过程中Fiber本身不变,两个queue也就这么产生了。
  2. > `alternate`workInProgress,currentalternate有一个映射关系
  3. <a name="createUpdateQueue"></a>
  4. ##### createUpdateQueue
  5. > createUpdateQueue 就是创建了对象,如下。
  6. > ```javascript
  7. // react-reconciler\src\ReactUpdateQueue.js
  8. export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  9. const queue: UpdateQueue<State> = {
  10. // 每次操作完更新之后的`state`
  11. baseState,
  12. // 队列中的第一个`Update`
  13. firstUpdate: null,
  14. // 队列中的最后一个`Update`
  15. lastUpdate: null,
  16. // 第一个捕获类型的`Update`
  17. firstCapturedUpdate: null,
  18. // 最后一个捕获类型的`Update`
  19. lastCapturedUpdate: null,
  20. // 第一个`side effect`
  21. firstEffect: null,
  22. // 最后一个`side effect`
  23. lastEffect: null,
  24. // 第一个和最后一个捕获产生的`side effect`
  25. firstCapturedEffect: null,
  26. lastCapturedEffect: null,
  27. };
  28. return queue;
  29. }

到此,React 将更新放到了Fiber队列中,接下来就是使用scheduleWork来进行调度了,这个后续章节讲。

③ legacyCreateRootFromDOMContainer

```jsx // react-dom\src\client\ReactDOM.js function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): _ReactSyncRoot { // 判断 forceHydrate 参数,render 函数插入的是false // shouldHydrateDueToLegacyHeuristic 函数主要是判断我们传入的Dom元素上是否有 data-reactroot 属性,这个属性一般是服务端渲染的时候赋给的 //[shouldHydrate]用作后续判断是否复用页面上的DOM节点 const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);

// 如果不复用页面上的 DOM 节点,那么调用 removeChild 方法将 DOM 节点的子节点全部清除 if (!shouldHydrate) { let warned = false; let rootSibling; //循环删除每一个子节点 while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } }

// 通过 new 创建对象 return new ReactSyncRoot( container, LegacyRoot,//LegacyRoot 是一个常量,代表的是传统的同步的渲染方式。 shouldHydrate //上面讲过 [这个属性一般是服务端渲染的时候赋给的,然后进行了判断],所以此处是判断是否为服务端渲染 ? { hydrate: true, } : undefined, ); }

  1. > LegacyRoot 是一个常量,代表的是传统的同步的渲染方式。
  2. > ```javascript
  3. // shared\ReactRootTags.js
  4. export const LegacyRoot = 0;
  5. export const BatchedRoot = 1;
  6. export const ConcurrentRoot = 2;

接着看 ReactSyncRoot 函数

④ ReactSyncRoot

创建ReactRoot的时候会调用DOMRenderer.createContainer创建FiberRoot,在后期调度更新的过程中这个节点非常重要

```jsx // react-dom\src\client\ReactDOM.js function ReactSyncRoot( container: DOMContainer, tag: RootTag, // Tag is either LegacyRoot or Concurrent Root options: void | RootOptions, //前方传来的[服务端渲染判断]相关参数 ) { const hydrate = options != null && options.hydrate === true; const hydrationCallbacks = (options != null && options.hydrationOptions) || null; const root = createContainer(container, tag, hydrate, hydrationCallbacks);

// 可以看出,new 出来的对象上,只有一个 _internalRoot 属性 this._internalRoot = root; }

  1. > 实际调用的是 `createContainer` 函数
  2. > 需要注意的是,如果不是ReactDOM.render,而是ReactDOM.unstable_createRoot,那么程序也会走到 createContainer 这步,但是 tag 就不是 RootTag,而是 ConcurrentRoot
  3. > ```javascript
  4. // react-reconciler\src\ReactFiberReconciler.js
  5. export function createContainer(
  6. containerInfo: Container,
  7. tag: RootTag, // 0
  8. hydrate: boolean, // false
  9. hydrationCallbacks: null | SuspenseHydrationCallbacks,
  10. ): OpaqueRoot {
  11. return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
  12. }

接着调用 createFiberRoot 函数

⑤ createFiberRoot

```javascript // react-reconciler\src\ReactFiberRoot.js export function createFiberRoot( containerInfo: any, // div tag: RootTag, // 0 hydrate: boolean,// false //是否服务端渲染 hydrationCallbacks: null | SuspenseHydrationCallbacks,// null —>根据是否服务端渲染决定此参数 ): FiberRoot {

// new 一个 FiberRoot 对象 const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);

// false 不用管 if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; }

// Cyclic construction. This cheats the type system right now because // stateNode is any. // 创建了一个Fiber对象,由于是root节点的Fiber const uninitializedFiber = createHostRootFiber(tag);

// FiberRoot 通过 current 指向 [uninitializedFiber] 对象 root.current = uninitializedFiber;

// [uninitializedFiber] 通过 stateNode 指向 dom 节点 uninitializedFiber.stateNode = root;

// 返回 FiberRoot return root; }

  1. > 调用 new FiberRootNode 创建了一个 FiberRoot 对象
  2. > ```javascript
  3. // react-reconciler\src\ReactFiberRoot.js
  4. function FiberRootNode(containerInfo, tag, hydrate) {
  5. // LegacyRoot | BatchedRoot | ConcurrentRoot
  6. this.tag = tag;
  7. // 当前应用对应的Fiber对象,是Root Fiber
  8. this.current = null;
  9. // root节点,render方法接收的第二个参数
  10. this.containerInfo = containerInfo;
  11. // react-dom不会用到
  12. this.pendingChildren = null;
  13. this.pingCache = null;
  14. this.finishedExpirationTime = NoWork;
  15. // 已经完成的任务的FiberRoot对象,如果你只有一个Root,那他永远只可能是这个Root对应的Fiber,或者是null
  16. // 在commit阶段只会处理这个值对应的任务
  17. this.finishedWork = null;
  18. this.timeoutHandle = noTimeout;
  19. // 顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会有用
  20. this.context = null;
  21. this.pendingContext = null;
  22. // 用来确定第一次渲染的时候是否需要融合
  23. this.hydrate = hydrate;
  24. this.firstBatch = null;
  25. this.callbackNode = null;
  26. this.callbackExpirationTime = NoWork;
  27. // 存在root中,最早的挂起时间
  28. // 不确定是否挂起的状态(所有任务一开始均是该状态)
  29. this.firstPendingTime = NoWork;
  30. // 存在root中,最新的挂起时间
  31. this.lastPendingTime = NoWork;
  32. this.pingTime = NoWork;
  33. }

通过 createHostRootFiber 创建一个Fiber对象

⑥ createHostRootFiber

此处会调用createFiberRoot函数

```javascript // react-reconciler\src\ReactFiber.js export function createHostRootFiber(tag: RootTag): Fiber {

// 通过tag来生成mode let mode; if (tag === ConcurrentRoot) { mode = ConcurrentMode | BatchedMode | StrictMode; } else if (tag === BatchedRoot) { mode = BatchedMode | StrictMode; } else { mode = NoMode; }

if (enableProfilerTimer && isDevToolsPresent) { // Always collect profile timings when DevTools are present. // This enables DevTools to start capturing timing at any point– // Without some nodes in the tree having empty base times. mode |= ProfileMode; }

// HostRoot 也是一个常量,用做标记,因为 root 节点的 Fiber 对象是Fiber树的头节点。后续会用的上 return createFiber(HostRoot, null, null, mode); } // react-reconciler\src\ReactTypeOfMode.js export const NoMode = 0b0000; export const StrictMode = 0b0001; // TODO: Remove BatchedMode and ConcurrentMode by reading from the root // tag instead export const BatchedMode = 0b0010; export const ConcurrentMode = 0b0100; export const ProfileMode = 0b1000;

  1. > 在生成 mode 的过程中,用到了上面这些常量,是二进制的值。0b0100 | 0b0001 = 0b0101。使用按位或操作,不仅能提高运行速度,在常量的判断上也很方便
  2. > 接着调用`createFiber`
  3. <a name="8d6a4cb1"></a>
  4. #### ⑦ createFiber
  5. > ```javascript
  6. // react-reconciler\src\ReactFiber.js
  7. const createFiber = function(
  8. tag: WorkTag,
  9. pendingProps: mixed,
  10. key: null | string,
  11. mode: TypeOfMode,
  12. ): Fiber {
  13. // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  14. return new FiberNode(tag, pendingProps, key, mode);
  15. };
  16. // react-reconciler\src\ReactFiber.js
  17. function FiberNode(
  18. tag: WorkTag,
  19. pendingProps: mixed,
  20. key: null | string,
  21. mode: TypeOfMode,
  22. ) {
  23. // Instance
  24. // 标记不同的组件类型
  25. this.tag = tag;
  26. // ReactElement里面的key
  27. this.key = key;
  28. // ReactElement.type,也就是我们调用`createElement`的第一个参数
  29. this.elementType = null;
  30. // 异步组件resolved之后返回的内容,一般是`function`或者`class`
  31. this.type = null;
  32. //跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)
  33. this.stateNode = null;
  34. // Fiber
  35. // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
  36. this.return = null;
  37. // 单链表树结构
  38. // 指向自己的第一个子节点
  39. this.child = null;
  40. // 指向自己的兄弟结构
  41. // 兄弟节点的return指向同一个父节点
  42. this.sibling = null;
  43. this.index = 0;
  44. // ref属性
  45. this.ref = null;
  46. // 新的变动带来的新的props
  47. this.pendingProps = pendingProps;
  48. // 上一次渲染完成之后的props
  49. this.memoizedProps = null;
  50. // 该Fiber对应的组件产生的Update会存放在这个队列里面
  51. this.updateQueue = null;
  52. // 上一次渲染的时候的state
  53. this.memoizedState = null;
  54. // 一个列表,存放这个Fiber依赖的context
  55. this.dependencies = null;
  56. // 用来描述当前Fiber和他子树的`Bitfield`
  57. // 共存的模式表示这个子树是否默认是异步渲染的
  58. // Fiber被创建的时候他会继承父Fiber
  59. // 其他的标识也可以在创建的时候被设置
  60. // 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
  61. this.mode = mode;
  62. // Effects
  63. // 用来记录Side Effect
  64. this.effectTag = NoEffect;
  65. // 单链表用来快速查找下一个side effect
  66. this.nextEffect = null;
  67. // 子树中第一个side effect
  68. this.firstEffect = null;
  69. // 子树中最后一个side effect
  70. this.lastEffect = null;
  71. // 代表任务在未来的哪个时间点应该被完成
  72. // 不包括他的子树产生的任务
  73. this.expirationTime = NoWork;
  74. // 快速确定子树中是否有不在等待的变化
  75. this.childExpirationTime = NoWork;
  76. // 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
  77. // 我们称他为`current <==> workInProgress`
  78. // 在渲染完成之后他们会交换位置
  79. this.alternate = null;
  80. ...
  81. }

终于把这个 RootFiber 创建出来了。接下来回到 legacyRenderSubtreeIntoContainer 函数,执行 updateContainer(children, fiberRoot, parentComponent, callback)。children是传入的 React 组件, fiberRoot 是一个 FiberRoot 对象,parentComponent 是 null。

updateContainer在前方②处已经给出详解—>② updateContainer

更多请看上方[React深入学习与源码解析笔记]