Repository: 7kms/react-illustration-series Branch: main Commit: 1e71a14310bc Files: 40 Total size: 327.7 KB Directory structure: gitextract_yrobj4id/ ├── .dumirc.ts ├── .editorconfig ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .husky/ │ ├── commit-msg │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── .prettierrc.js ├── docs/ │ ├── algorithm/ │ │ ├── bitfield.md │ │ ├── dfs.md │ │ ├── diff.md │ │ ├── heapsort.md │ │ ├── linkedlist.md │ │ └── stack.md │ ├── index.md │ ├── interview/ │ │ ├── 01-setstate.md │ │ ├── 06-key.md │ │ └── index.md │ └── main/ │ ├── bootstrap.md │ ├── context.md │ ├── fibertree-commit.md │ ├── fibertree-create.md │ ├── fibertree-prepare.md │ ├── fibertree-update.md │ ├── hook-effect.md │ ├── hook-state.md │ ├── hook-summary.md │ ├── macro-structure.md │ ├── object-structure.md │ ├── priority.md │ ├── reconciler-workflow.md │ ├── scheduler.md │ ├── state-effects.md │ ├── synthetic-event.md │ └── workloop.md ├── licence ├── package.json ├── readme.md └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dumirc.ts ================================================ import { defineConfig } from 'dumi'; const isProd = process.env.NODE_ENV !== 'development'; const ghPagePublicPath = isProd ? '/' : '/'; export default defineConfig({ publicPath: ghPagePublicPath, base: ghPagePublicPath, favicons: [`${ghPagePublicPath}km@2x.png`], themeConfig: { name: '图解React', logo: `${ghPagePublicPath}logo.png`, socialLinks:{ github: 'https://github.com/7kms/react-illustration-series', } }, hash: true, exportStatic: {}, ssr: isProd ? {} : false, metas: [ { name: 'keywords', content: 'react, react原理, 图解react, react fiber原理, react hook原理, react 合成事件, react 基本包结构', }, { name: 'description', content: '图解React原理系列, 以react核心包结构和运行机制为主线索进行展开. 包括react 基本包结构, react 工作循环, react 启动模式, react fiber原理, react hook原理, react 合成事件等核心内容', }, ], }); ================================================ FILE: .editorconfig ================================================ # http://editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - main - master pull_request: branches: - main - master jobs: deploy: name: Deployment on Node ${{ matrix.node-version }} ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] node-version: [16] steps: - name: Checkout repository uses: actions/checkout@v2 with: fetch-depth: 0 # Keep `0` for dumi last updated time feature - name: Setup Node environment uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ cache: 'npm' - name: Install dependencies run: | npm ci - name: Building work run: | npm run build - name: Deploy to Github Pages uses: peaceiris/actions-gh-pages@v3 if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }} with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dist force_orphan: true user_name: 'github-actions[bot]' user_email: 'github-actions[bot]@users.noreply.github.com' commit_message: ${{ github.event.head_commit.message }} ================================================ FILE: .gitignore ================================================ node_modules /dist .dumi/tmp .dumi/tmp-production .DS_Store /server ================================================ FILE: .husky/commit-msg ================================================ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npx commitlint --edit "${1}" ================================================ FILE: .husky/pre-commit ================================================ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npx lint-staged ================================================ FILE: .prettierignore ================================================ .dumi/tmp .dumi/tmp-production *.yaml ================================================ FILE: .prettierrc ================================================ { "singleQuote": true, "trailingComma": "all", "printWidth": 80, "overrides": [ { "files": ".prettierrc", "options": { "parser": "json" } } ] } ================================================ FILE: .prettierrc.js ================================================ module.exports = { printWidth: 80, proseWrap: 'never', singleQuote: true, trailingComma: 'all', overrides: [ { files: '*.md', options: { proseWrap: 'preserve', }, }, ], }; ================================================ FILE: docs/algorithm/bitfield.md ================================================ --- title: 位运算 order: 1 --- # React 算法之位运算 网络上介绍位运算的文章非常多(如[MDN 上的介绍](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators)就很仔细). 本文的目的: 1. 温故知新, 对位运算的基本使用做一下简单的总结. 2. 归纳在`javascript`中使用位运算的注意事项. 3. 列举在`react`源码中, 对于位运算的高频使用场景. ## 概念 位运算直接处理每一个比特位(bit), 是非常底层的运算, 优势是速度快, 劣势就是不直观且只支持整数运算. ## 特性 | 位运算 | 用法 | 描述 | | ----------------- | --------- | --------------------------------------------------------------------------- | | 按位与(`&`) | `a & b` | 对于每一个比特位,两个操作数都为 1 时, 结果为 1, 否则为 0 | | 按位或(`\|`) | `a \| b` | 对于每一个比特位,两个操作数都为 0 时, 结果为 0, 否则为 1 | | 按位异或(`^`) | `a ^ b` | 对于每一个比特位,两个操作数相同时, 结果为 0, 否则为 1 | | 按位非(`~`) | `~ a` | 反转操作数的比特位, 即 0 变成 1, 1 变成 0 | | 左移(`<<`) | `a << b` | 将 a 的二进制形式向左移 b (< 32) 比特位, 右边用 0 填充 | | 有符号右移(`>>`) | `a >> b` | 将 a 的二进制形式向右移 b (< 32) 比特位, 丢弃被移除的位, 左侧以最高位来填充 | | 无符号右移(`>>>`) | `a >>> b` | 将 a 的二进制形式向右移 b (< 32) 比特位, 丢弃被移除的位, 并用 0 在左侧填充 | 在[`ES5`规范中](https://www.ecma-international.org/ecma-262/5.1/#sec-11.10), 对二进制位运算的说明如下: ``` The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows: 1. Let lref be the result of evaluating A. 2. Let lval be GetValue(lref). 3. Let rref be the result of evaluating B. 4. Let rval be GetValue(rref). 5. Let lnum be ToInt32(lval). 6. Let rnum be ToInt32(rval). 7. Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer. ``` 意思是会将位运算中的左右操作数都转换为`有符号32位整型`, 且返回结果也是`有符号32位整型` - 所以当操作数是浮点型时首先会被转换成整型, 再进行位运算 - 当操作数过大, 超过了`Int32`范围, 超过的部分会被截取 通过以上知识的回顾, 要点如下: 1. 位运算只能在整型变量之间进行运算 2. js 中的`Number`类型在底层都是以浮点数(参考 IEEE754 标准)进行存储. 3. js 中所有的按位操作符的操作数都会被[转成补码(two's complement)](https://www.ecma-international.org/ecma-262/5.1/#sec-9.5)形式的`有符号32位整数`. 所以在 js 中使用位运算时, 有 2 种情况会造成结果异常: 1. 操作数为浮点型(虽然底层都是浮点型, 此处理解为显示性的浮点型) - 转换流程: 浮点数 -> 整数(丢弃小数位) -> 位运算 2. 操作数的大小超过`Int32`范围(`-2^31 ~ 2^31-1`). 超过范围的二进制位会被截断, 取`低位32bit`. ``` Before: 11100110111110100000000000000110000000000001 After: 10100000000000000110000000000001 ``` 另外由于 js 语言的[隐式转换](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness), 对非`Number`类型使用位运算操作符时会发生隐式转换, 相当于先使用`Number(xxx)`将其转换为`number`类型, 再进行位运算: ```js 'str' >>> 0; // ===> Number('str') >>> 0 ===> NaN >>> 0 = 0 ``` ## 基本使用 为了方便比较, 以下演示代码中的注释, 都写成了 8 位二进制数(上文已经说明, 事实上在 js 中, 位运算最终的结果都是 Int32). 枚举属性: 通过位移的方式, 定义一些枚举常量 ```js const A = 1 << 0; // 0b00000001 const B = 1 << 1; // 0b00000010 const C = 1 << 2; // 0b00000100 ``` 位掩码: 通过位移定义的一组枚举常量, 可以利用位掩码的特性, 快速操作这些枚举产量(增加, 删除, 比较). 1. 属性增加`|` 1. `ABC = A | B | C` 2. 属性删除`& ~` 1. `AB = ABC & ~C` 3. 属性比较 1. AB 当中包含 B: `AB & B === B` 2. AB 当中不包含 C: `AB & C === 0` 3. A 和 B 相等: `A === B` ```js const A = 1 << 0; // 0b00000001 const B = 1 << 1; // 0b00000010 const C = 1 << 2; // 0b00000100 // 增加属性 const ABC = A | B | C; // 0b00000111 // 删除属性 const AB = ABC & ~C; // 0b00000011 // 属性比较 // 1. AB当中包含B console.log((AB & B) === B); // true // 2. AB当中不包含C console.log((AB & C) === 0); // true // 3. A和B相等 console.log(A === B); // false ``` ## React 当中的使用场景 在 react 核心包中, 位运算使用的场景非常多. 此处只列举出了使用频率较高的示例. ### 优先级管理 lanes lanes 是`17.x`版本中开始引入的重要概念, 代替了`16.x`版本中的`expirationTime`, 作为`fiber`对象的一个属性(位于`react-reconciler`包), 主要控制 fiber 树在构造过程中的优先级(这里只介绍位运算的应用, 对于 lanes 的深入分析在[`优先级管理`](../main/priority.md)章节深入解读). 变量定义: 首先看源码[ReactFiberLane.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L74-L103)中的定义 ```js //类型定义 export opaque type Lanes = number; export opaque type Lane = number; // 变量定义 export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000; export const NoLane: Lane = /* */ 0b0000000000000000000000000000000; export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001; export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010; export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100; const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000; const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000; const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000; // ... // ... const NonIdleLanes = /* */ 0b0000111111111111111111111111111; export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000; export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000; ``` 源码中`Lanes`和`Lane`都是`number`类型, 并且将所有变量都使用二进制位来表示. 注意: 源码中变量只列出了 31 位, 由于 js 中位运算都会转换成`Int32`(上文已经解释), 最多为 32 位, 且最高位是符号位. 所以除去符号位, 最多只有 31 位可以参与运算. [方法定义](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L121-L194): ```js function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { // 判断 lanes中是否包含 SyncLane if ((SyncLane & lanes) !== NoLanes) { return_highestLanePriority = SyncLanePriority; return SyncLane; } // 判断 lanes中是否包含 SyncBatchedLane if ((SyncBatchedLane & lanes) !== NoLanes) { return_highestLanePriority = SyncBatchedLanePriority; return SyncBatchedLane; } // ... // ... 省略其他代码 return lanes; } ``` 在方法定义中, 也是通过位掩码的特性来判断二进制形式变量之间的关系. 除了常规的位掩码操作外, 特别说明其中 2 个技巧性强的函数: 1. `getHighestPriorityLane`: 分离出最高优先级 ```js function getHighestPriorityLane(lanes: Lanes) { return lanes & -lanes; } ``` 通过`lanes & -lanes`可以分离出所有比特位中最右边的 1, 具体来讲: - 假设 `lanes(InputDiscreteLanes) = 0b0000000000000000000000000011000` - 那么 `-lanes = 0b1111111111111111111111111101000` - 所以 `lanes & -lanes = 0b0000000000000000000000000001000` - 相比最初的 InputDiscreteLanes, 分离出来了`最右边的1` - 通过 lanes 的定义, 数字越小的优先级越高, 所以此方法可以获取`最高优先级的lane` - 2. `getLowestPriorityLane`: 分离出最低优先级 ```js function getLowestPriorityLane(lanes: Lanes): Lane { // This finds the most significant non-zero bit. const index = 31 - clz32(lanes); return index < 0 ? NoLanes : 1 << index; } ``` `clz32(lanes)`返回一个数字在转换成 32 无符号整形数字的二进制形式后, 前导 0 的个数([MDN 上的解释](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32)) - 假设 `lanes(InputDiscreteLanes) = 0b0000000000000000000000000011000` - 那么 `clz32(lanes) = 27`, 由于 InputDiscreteLanes 在源码中被书写成了 31 位, 虽然在字面上前导 0 是 26 个, 但是转成标准 32 位后是 27 个 - `index = 31 - clz32(lanes) = 4` - 最后 `1 << index = 0b0000000000000000000000000010000` - 相比最初的 InputDiscreteLanes, 分离出来了`最左边的1` - 通过 lanes 的定义, 数字越小的优先级越高, 所以此方法可以获取最低优先级的 lane ### 执行上下文 ExecutionContext `ExecutionContext`定义与`react-reconciler`包中, 代表`reconciler`在运行时的上下文状态(在`reconciler 执行上下文`章节中深入解读, 此处介绍位运算的应用). [变量定义](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L247-L256): ```js export const NoContext = /* */ 0b0000000; const BatchedContext = /* */ 0b0000001; const EventContext = /* */ 0b0000010; const DiscreteEventContext = /* */ 0b0000100; const LegacyUnbatchedContext = /* */ 0b0001000; const RenderContext = /* */ 0b0010000; const CommitContext = /* */ 0b0100000; export const RetryAfterError = /* */ 0b1000000; // ... // Describes where we are in the React execution stack let executionContext: ExecutionContext = NoContext; ``` 注意: 和`lanes`的定义不同, `ExecutionContext`类型的变量, 在定义的时候采取的是 8 位二进制表示(因为变量的数量少, 8 位就够了, 没有必要写成 31 位). 使用(由于使用的地方较多, 所以举一个[代表性强的例子](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619), `scheduleUpdateOnFiber` 函数是`react-reconciler`包对`react`包暴露出来的 api, 每一次更新都会调用, 所以比较特殊): ```js // scheduleUpdateOnFiber函数中包含了好多关于executionContext的判断(都是使用位运算) export function scheduleUpdateOnFiber( fiber: Fiber, lane: Lane, eventTime: number, ) { if (root === workInProgressRoot) { // 判断: executionContext 不包含 RenderContext if ( deferRenderPhaseUpdateToNextBatch || (executionContext & RenderContext) === NoContext ) { // ... } } if (lane === SyncLane) { if ( // 判断: executionContext 包含 LegacyUnbatchedContext (executionContext & LegacyUnbatchedContext) !== NoContext && // 判断: executionContext 不包含 RenderContext或CommitContext (executionContext & (RenderContext | CommitContext)) === NoContext ) { // ... } } // ... } ``` ## 总结 本节介绍了位运算的基本使用, 并列举了位运算在`react`源码中的高频应用. 在特定的情况下, 使用位运算不仅是提高运算速度, 且位掩码能简洁和清晰的表示出二进制变量之间的关系. 二进制变量虽然有优势, 但是缺点也很明显, 不够直观, 扩展性不好(在 js 当中的二进制变量, 除去符号位, 最多只能使用 31 位, 当变量的数量超过 31 位就需要组合, 此时就会变得复杂). 在阅读源码时, 我们需要了解二级制变量和位掩码的使用. 但在实际开发中, 需要视情况而定, 不能盲目使用. ## 参考资料 [ECMAScript® Language Specification(Standard ECMA-262 5.1 Edition) Binary Bitwise Operators](https://www.ecma-international.org/ecma-262/5.1/#sec-11.10) [浮点数的二进制表示](https://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html) [IEEE 754](https://zh.wikipedia.org/wiki/IEEE_754) ================================================ FILE: docs/algorithm/dfs.md ================================================ --- title: 深度优先遍历 order: 2 --- # React 算法之深度优先遍历 对于树或图结构的搜索(或遍历)来讲, 分为深度优先(DFS)和广度优先(BFS). ## 概念 深度优先遍历: DFS(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法. 来自 wiki 上的解释(更权威): 当`节点v`的所在边都己被探寻过, 搜索将回溯到发现`节点v`的那条边的起始节点. 这一过程一直进行到已发现从源节点可达的所有节点为止. 如果还存在未被发现的节点, 则选择其中一个作为源节点并重复以上过程, 整个进程反复进行直到所有节点都被访问为止. ## 实现方式 DFS 的主流实现方式有 2 种. 1. 递归(简单粗暴) 2. 利用`栈`存储遍历路径 ```js function Node() { this.name = ''; this.children = []; } function dfs(node) { console.log('探寻阶段: ', node.name); node.children.forEach((child) => { dfs(child); }); console.log('回溯阶段: ', node.name); } ``` 2. 使用栈 ```js function Node() { this.name = ''; this.children = []; // 因为要分辨探寻阶段和回溯阶段, 所以必须要一个属性来记录是否已经访问过该节点 // 如果不打印探寻和回溯, 就不需要此属性 this.visited = false; } function dfs(node) { const stack = []; stack.push(node); // 栈顶元素还存在, 就继续循环 while ((node = stack[stack.length - 1])) { if (node.visited) { console.log('回溯阶段: ', node.name); // 回溯完成, 弹出该元素 stack.pop(); } else { console.log('探寻阶段: ', node.name); node.visited = true; // 利用栈的先进后出的特性, 倒序将节点送入栈中 for (let i = node.children.length - 1; i >= 0; i--) { stack.push(node.children[i]); } } } } ``` ## React 当中的使用场景 深度优先遍历在`react`当中的使用非常典型, 最主要的使用时在`ReactElement`和`fiber`树的构造过程. 其次是在使用`context`时, 需要深度优先地查找消费`context`的节点. ### ReactElement "树"的构造 `ReactElement`不能算是严格的树结构, 为了方便表述, 后文都称之为树. 在`react-reconciler`包中, `ReactElement`的构造过程实际上是嵌套在`fiber树构造循环`过程中的, 与`fiber`树的构造是相互交替进行的(在`fiber 树构建`章节中详细解读, 本节只介绍深度优先遍历的使用场景). `ReactElement`树的构造, 实际上就是各级组件`render`之后的总和. 整个过程体现在`reconciler`工作循环之中. 源码位于[`ReactFiberWorkLoop.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1558)中, 此处为了简明, 已经将源码中与 dfs 无关的旁支逻辑去掉. ```js function workLoopSync() { // 1. 最外层循环, 保证每一个节点都能遍历, 不会遗漏 while (workInProgress !== null) { performUnitOfWork(workInProgress); } } function performUnitOfWork(unitOfWork: Fiber): void { const current = unitOfWork.alternate; let next; // 2. beginWork是向下探寻阶段 next = beginWork(current, unitOfWork, subtreeRenderLanes); if (next === null) { // 3. completeUnitOfWork 是回溯阶段 completeUnitOfWork(unitOfWork); } else { workInProgress = next; } } function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; do { const current = completedWork.alternate; const returnFiber = completedWork.return; let next; // 3.1 回溯并处理节点 next = completeWork(current, completedWork, subtreeRenderLanes); if (next !== null) { // 判断在处理节点的过程中, 是否派生出新的节点 workInProgress = next; return; } const siblingFiber = completedWork.sibling; // 3.2 判断是否有旁支 if (siblingFiber !== null) { workInProgress = siblingFiber; return; } // 3.3 没有旁支 继续回溯 completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null); } ``` 以上源码本质上是采用递归的方式进行 dfs, 假设有以下组件结构: ```js class App extends React.Component { render() { return (
1
2
3
注意:
- `ReactElement`树是在大循环中的`beginWork`阶段"逐级"生成的.
- "逐级"中的每一级是指一个`class`或`function`类型的组件, 每调用一次`render`或执行一次`function`调用, 就会生成一批`ReactElement`节点.
- `ReactElement`树的构造, 实际上就是各级组件`render`之后的总和.
### fiber 树的构造
在`ReactElement`的构造过程中, 同时伴随着`fiber`树的构造, `fiber`树同样也是在`beginWork`阶段生成的.
绘制出遍历路径如下:

### 查找 context 的消费节点
当`context`改变之后, 需要找出依赖该`context`的所有子节点(详细分析会在`context原理`章节深入解读), 这里同样也是一个`DFS`, 具体源码在[ReactFiberNewContext.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L182-L295).
将其主干逻辑剥离出来, 可以清晰的看出采用循环递归的方式进行遍历:
```js
export function propagateContextChange(
workInProgress: Fiber,
context: ReactContext
注意: 此处只表示出链表的结构示意图, 在`fiber 树构造`章节中会对上图的结构进行详细解读.
2. `updateQueue`链表(链式队列): 存储将要更新的状态, 构成该队列的元素是`update`对象
- `fiber.updateQueue.pending`: 存储`state`更新的队列(链式队列), `class`类型节点的`state`改动之后, 都会创建一个`update`对象添加到这个队列中. 由于此队列是一个环形队列, 为了方便添加新元素和快速拿到队首元素, 所以`pending`指针指向了队列中最后一个元素.

注意: 此处只表示出链表的结构示意图, 在`状态组件(class 与 function)`章节中会对上图的结构进行详细解读.
### Hook 对象
在[react 高频对象](../main/object-structure.md#Hook)中对`Hook`对象的属性做了说明, `Hook`对象具备`.next`属性, 所以`Hook`对象本身就是链表中的一个节点.
此外`hook.queue.pending`也构成了一个链表, 将`hook`链表与`hook.queue.pending`链表同时表示在图中, 得到的结构如下:

注意: 此处只表示出链表的结构示意图, 在`hook 原理`章节中会对上图的结构进行详细解读.
### 链表合并
在`react`中, 发起更新之后, 会通过`链表合并`的方式把等待(`pending`状态)更新的队列(`updateQueue`)合并到基础队列(`class`组件:`fiber.updateQueue.firstBaseUpdate`;`function`组件: `hook.baseQueue`), 最后通过遍历`baseQueue`筛选出优先级足够的`update`对象, 组合成最终的组件状态(`state`). 这个过程发生在`reconciler`阶段, 分别涉及到`class`组件和`function`组件.
具体场景:
1. `class`组件中
- 在`class`组件中调用`setState`, 会创建`update`对象并添加到`fiber.updateQueue.shared.pending`链式队列([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactUpdateQueue.old.js#L198-L230)).
```js
export function enqueueUpdate当前count={this.state.count}
1
2
{item}
))}1
2
3
注意:
- `class`和`function`类型的组件,其子节点是在 render 之后(`reconciler`阶段)才生成的. 此处只是单独表示`ReactElement`的数据结构.
- 父级对象和子级对象之间是通过`props.children`属性进行关联的(与 fiber 树不同).
- `ReactElement`虽然不能算是一个严格的树, 也不能算是一个严格的链表. 它的生成过程是自顶向下的, 是所有组件节点的总和.
- `ReactElement`树(暂且用树来表述)和`fiber`树是以`props.children`为单位`先后交替`生成的(在 fiber 树构建章节详细解读), 当`ReactElement`树构造完毕, fiber 树也随后构造完毕.
- `reconciler`阶段会根据`ReactElement`的类型生成对应的`fiber`节点(不是一一对应, 比如`Fragment`类型的组件在生成`fiber`节点的时候会略过).
## `react-reconciler` 包
在[宏观结构](./macro-structure.md)中介绍过, `react-reconciler`包是`react`应用的中枢, 连接渲染器(`react-dom`)和调度中心(`scheduler`), 同时自身也负责 fiber 树的构造.
对于此包的深入分析, 放在`fiber 树构建`, `reconciler 工作空间`等章节中.
此处先要知道`fiber`是核心, react 体系的渲染和更新都要以 fiber 作为数据模型, 如果不能理解 fiber, 也无法深入理解 react.
本章先预览一下此包中与`fiber`对象关联度较高的对象.
### Fiber 对象
先看数据结构, 其 type 类型的定义在[`ReactInternalTypes.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactInternalTypes.js#L47-L174)中:
```js
// 一个Fiber对象代表一个即将渲染或者已经渲染的组件(ReactElement), 一个组件可能对应两个fiber(current和WorkInProgress)
// 单个属性的解释在后文(在注释中无法添加超链接)
export type Fiber = {|
tag: WorkTag,
key: null | string,
elementType: any,
type: any,
stateNode: any,
return: Fiber | null,
child: Fiber | null,
sibling: Fiber | null,
index: number,
ref:
| null
| (((handle: mixed) => void) & { _stringRef: ?string, ... })
| RefObject,
pendingProps: any, // 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动
memoizedProps: any, // 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中
updateQueue: mixed, // 存储state更新的队列, 当前节点的state改动之后, 都会创建一个update对象添加到这个队列中.
memoizedState: any, // 用于输出的state, 最终渲染所使用的state
dependencies: Dependencies | null, // 该fiber节点所依赖的(contexts, events)等
mode: TypeOfMode, // 二进制位Bitfield,继承至父节点,影响本fiber节点及其子树中所有节点. 与react应用的运行模式有关(有ConcurrentMode, BlockingMode, NoMode等选项).
// Effect 副作用相关
flags: Flags, // 标志位
subtreeFlags: Flags, //替代16.x版本中的 firstEffect, nextEffect. 当设置了 enableNewReconciler=true才会启用
deletions: Array