[
  {
    "path": ".dumirc.ts",
    "content": "import { defineConfig } from 'dumi';\n\nconst isProd = process.env.NODE_ENV !== 'development';\nconst ghPagePublicPath = isProd ? '/' : '/';\n\n\nexport default defineConfig({\n  publicPath: ghPagePublicPath,\n  base: ghPagePublicPath,\n  favicons: [`${ghPagePublicPath}km@2x.png`],\n  themeConfig: {\n    name: '图解React',\n    \n    logo: `${ghPagePublicPath}logo.png`,\n \n    socialLinks:{\n      github: 'https://github.com/7kms/react-illustration-series',\n    }\n  },\n  hash: true,\n  exportStatic: {},\n  ssr: isProd ? {} : false,\n  metas: [\n    {\n      name: 'keywords',\n      content:\n        'react, react原理, 图解react, react fiber原理, react hook原理, react 合成事件, react 基本包结构',\n    },\n    {\n      name: 'description',\n      content:\n        '图解React原理系列, 以react核心包结构和运行机制为主线索进行展开. 包括react 基本包结构, react 工作循环, react 启动模式, react fiber原理, react hook原理, react 合成事件等核心内容',\n    },\n  ],\n});\n"
  },
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request:\n    branches:\n      - main\n      - master\n\njobs:\n  deploy:\n    name: Deployment on Node ${{ matrix.node-version }} ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n        node-version: [16]\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0 # Keep `0` for dumi last updated time feature\n      - name: Setup Node environment\n        uses: actions/setup-node@v2\n        with:\n          node-version: ${{ matrix.node-version }}\n          registry-url: https://registry.npmjs.org/\n          cache: 'npm'\n      - name: Install dependencies\n        run: |\n          npm ci\n      - name: Building work\n        run: |\n          npm run build\n      - name: Deploy to Github Pages\n        uses: peaceiris/actions-gh-pages@v3\n        if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }}\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./dist\n          force_orphan: true\n          user_name: 'github-actions[bot]'\n          user_email: 'github-actions[bot]@users.noreply.github.com'\n          commit_message: ${{ github.event.head_commit.message }}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n/dist\n.dumi/tmp\n.dumi/tmp-production\n.DS_Store\n/server"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx commitlint --edit \"${1}\"\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "content": ".dumi/tmp\n.dumi/tmp-production\n*.yaml\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"printWidth\": 80,\n  \"overrides\": [\n    {\n      \"files\": \".prettierrc\",\n      \"options\": { \"parser\": \"json\" }\n    }\n  ]\n}\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  printWidth: 80,\n  proseWrap: 'never',\n  singleQuote: true,\n  trailingComma: 'all',\n  overrides: [\n    {\n      files: '*.md',\n      options: {\n        proseWrap: 'preserve',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "docs/algorithm/bitfield.md",
    "content": "---\ntitle: 位运算\norder: 1\n---\n\n# React 算法之位运算\n\n网络上介绍位运算的文章非常多(如[MDN 上的介绍](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators)就很仔细).\n\n本文的目的:\n\n1. 温故知新, 对位运算的基本使用做一下简单的总结.\n2. 归纳在`javascript`中使用位运算的注意事项.\n3. 列举在`react`源码中, 对于位运算的高频使用场景.\n\n## 概念\n\n位运算直接处理每一个比特位(bit), 是非常底层的运算, 优势是速度快, 劣势就是不直观且只支持整数运算.\n\n## 特性\n\n| 位运算            | 用法      | 描述                                                                        |\n| ----------------- | --------- | --------------------------------------------------------------------------- |\n| 按位与(`&`)       | `a & b`   | 对于每一个比特位,两个操作数都为 1 时, 结果为 1, 否则为 0                    |\n| 按位或(`\\|`)      | `a \\| b`  | 对于每一个比特位,两个操作数都为 0 时, 结果为 0, 否则为 1                    |\n| 按位异或(`^`)     | `a ^ b`   | 对于每一个比特位,两个操作数相同时, 结果为 0, 否则为 1                       |\n| 按位非(`~`)       | `~ a`     | 反转操作数的比特位, 即 0 变成 1, 1 变成 0                                   |\n| 左移(`<<`)        | `a << b`  | 将 a 的二进制形式向左移 b (< 32) 比特位, 右边用 0 填充                      |\n| 有符号右移(`>>`)  | `a >> b`  | 将 a 的二进制形式向右移 b (< 32) 比特位, 丢弃被移除的位, 左侧以最高位来填充 |\n| 无符号右移(`>>>`) | `a >>> b` | 将 a 的二进制形式向右移 b (< 32) 比特位, 丢弃被移除的位, 并用 0 在左侧填充  |\n\n在[`ES5`规范中](https://www.ecma-international.org/ecma-262/5.1/#sec-11.10), 对二进制位运算的说明如下:\n\n```\nThe production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:\n1. Let lref be the result of evaluating A.\n2. Let lval be GetValue(lref).\n3. Let rref be the result of evaluating B.\n4. Let rval be GetValue(rref).\n5. Let lnum be ToInt32(lval).\n6. Let rnum be ToInt32(rval).\n7. Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer.\n```\n\n意思是会将位运算中的左右操作数都转换为`有符号32位整型`, 且返回结果也是`有符号32位整型`\n\n- 所以当操作数是浮点型时首先会被转换成整型, 再进行位运算\n- 当操作数过大, 超过了`Int32`范围, 超过的部分会被截取\n\n通过以上知识的回顾, 要点如下:\n\n1. 位运算只能在整型变量之间进行运算\n2. js 中的`Number`类型在底层都是以浮点数(参考 IEEE754 标准)进行存储.\n3. js 中所有的按位操作符的操作数都会被[转成补码（two's complement）](https://www.ecma-international.org/ecma-262/5.1/#sec-9.5)形式的`有符号32位整数`.\n\n所以在 js 中使用位运算时, 有 2 种情况会造成结果异常:\n\n1. 操作数为浮点型(虽然底层都是浮点型, 此处理解为显示性的浮点型)\n   - 转换流程: 浮点数 -> 整数(丢弃小数位) -> 位运算\n2. 操作数的大小超过`Int32`范围(`-2^31 ~ 2^31-1`). 超过范围的二进制位会被截断, 取`低位32bit`.\n\n   ```\n         Before: 11100110111110100000000000000110000000000001\n         After:              10100000000000000110000000000001\n   ```\n\n另外由于 js 语言的[隐式转换](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness), 对非`Number`类型使用位运算操作符时会发生隐式转换, 相当于先使用`Number(xxx)`将其转换为`number`类型, 再进行位运算:\n\n```js\n'str' >>> 0; //  ===> Number('str') >>> 0  ===> NaN >>> 0 = 0\n```\n\n## 基本使用\n\n为了方便比较, 以下演示代码中的注释, 都写成了 8 位二进制数(上文已经说明, 事实上在 js 中, 位运算最终的结果都是 Int32).\n\n枚举属性:\n\n通过位移的方式, 定义一些枚举常量\n\n```js\nconst A = 1 << 0; // 0b00000001\nconst B = 1 << 1; // 0b00000010\nconst C = 1 << 2; // 0b00000100\n```\n\n位掩码:\n\n通过位移定义的一组枚举常量, 可以利用位掩码的特性, 快速操作这些枚举产量(增加, 删除, 比较).\n\n1. 属性增加`|`\n   1. `ABC = A | B | C`\n2. 属性删除`& ~`\n   1. `AB = ABC & ~C`\n3. 属性比较\n   1. AB 当中包含 B: `AB & B === B`\n   2. AB 当中不包含 C: `AB & C === 0`\n   3. A 和 B 相等: `A === B`\n\n```js\nconst A = 1 << 0; // 0b00000001\nconst B = 1 << 1; // 0b00000010\nconst C = 1 << 2; // 0b00000100\n\n// 增加属性\nconst ABC = A | B | C; // 0b00000111\n// 删除属性\nconst AB = ABC & ~C; // 0b00000011\n\n// 属性比较\n// 1. AB当中包含B\nconsole.log((AB & B) === B); // true\n// 2. AB当中不包含C\nconsole.log((AB & C) === 0); // true\n// 3. A和B相等\nconsole.log(A === B); // false\n```\n\n## React 当中的使用场景\n\n在 react 核心包中, 位运算使用的场景非常多. 此处只列举出了使用频率较高的示例.\n\n### 优先级管理 lanes\n\nlanes 是`17.x`版本中开始引入的重要概念, 代替了`16.x`版本中的`expirationTime`, 作为`fiber`对象的一个属性(位于`react-reconciler`包), 主要控制 fiber 树在构造过程中的优先级(这里只介绍位运算的应用, 对于 lanes 的深入分析在[`优先级管理`](../main/priority.md)章节深入解读).\n\n变量定义:\n\n首先看源码[ReactFiberLane.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L74-L103)中的定义\n\n```js\n//类型定义\nexport opaque type Lanes = number;\nexport opaque type Lane = number;\n\n// 变量定义\nexport const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;\nexport const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;\n\nexport const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;\nexport const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;\n\nexport const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;\nconst InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;\n\nconst InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;\nconst InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;\n// ...\n// ...\n\nconst NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;\n\nexport const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;\nconst IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;\n\nexport const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;\n```\n\n源码中`Lanes`和`Lane`都是`number`类型, 并且将所有变量都使用二进制位来表示.\n\n注意: 源码中变量只列出了 31 位, 由于 js 中位运算都会转换成`Int32`(上文已经解释), 最多为 32 位, 且最高位是符号位. 所以除去符号位, 最多只有 31 位可以参与运算.\n\n[方法定义](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L121-L194):\n\n```js\nfunction getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {\n  // 判断 lanes中是否包含 SyncLane\n  if ((SyncLane & lanes) !== NoLanes) {\n    return_highestLanePriority = SyncLanePriority;\n    return SyncLane;\n  }\n  // 判断 lanes中是否包含 SyncBatchedLane\n  if ((SyncBatchedLane & lanes) !== NoLanes) {\n    return_highestLanePriority = SyncBatchedLanePriority;\n    return SyncBatchedLane;\n  }\n  // ...\n  // ... 省略其他代码\n  return lanes;\n}\n```\n\n在方法定义中, 也是通过位掩码的特性来判断二进制形式变量之间的关系. 除了常规的位掩码操作外, 特别说明其中 2 个技巧性强的函数:\n\n1. `getHighestPriorityLane`: 分离出最高优先级\n\n```js\nfunction getHighestPriorityLane(lanes: Lanes) {\n  return lanes & -lanes;\n}\n```\n\n通过`lanes & -lanes`可以分离出所有比特位中最右边的 1, 具体来讲:\n\n- 假设 `lanes(InputDiscreteLanes) = 0b0000000000000000000000000011000`\n- 那么 `-lanes = 0b1111111111111111111111111101000`\n- 所以 `lanes & -lanes = 0b0000000000000000000000000001000`\n- 相比最初的 InputDiscreteLanes, 分离出来了`最右边的1`\n- 通过 lanes 的定义, 数字越小的优先级越高, 所以此方法可以获取`最高优先级的lane`\n-\n\n2. `getLowestPriorityLane`: 分离出最低优先级\n\n```js\nfunction getLowestPriorityLane(lanes: Lanes): Lane {\n  // This finds the most significant non-zero bit.\n  const index = 31 - clz32(lanes);\n  return index < 0 ? NoLanes : 1 << index;\n}\n```\n\n`clz32(lanes)`返回一个数字在转换成 32 无符号整形数字的二进制形式后, 前导 0 的个数([MDN 上的解释](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32))\n\n- 假设 `lanes(InputDiscreteLanes) = 0b0000000000000000000000000011000`\n- 那么 `clz32(lanes) = 27`, 由于 InputDiscreteLanes 在源码中被书写成了 31 位, 虽然在字面上前导 0 是 26 个, 但是转成标准 32 位后是 27 个\n- `index = 31 - clz32(lanes) = 4`\n- 最后 `1 << index = 0b0000000000000000000000000010000`\n- 相比最初的 InputDiscreteLanes, 分离出来了`最左边的1`\n- 通过 lanes 的定义, 数字越小的优先级越高, 所以此方法可以获取最低优先级的 lane\n\n### 执行上下文 ExecutionContext\n\n`ExecutionContext`定义与`react-reconciler`包中, 代表`reconciler`在运行时的上下文状态(在`reconciler 执行上下文`章节中深入解读, 此处介绍位运算的应用).\n\n[变量定义](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L247-L256):\n\n```js\nexport const NoContext = /*             */ 0b0000000;\nconst BatchedContext = /*               */ 0b0000001;\nconst EventContext = /*                 */ 0b0000010;\nconst DiscreteEventContext = /*         */ 0b0000100;\nconst LegacyUnbatchedContext = /*       */ 0b0001000;\nconst RenderContext = /*                */ 0b0010000;\nconst CommitContext = /*                */ 0b0100000;\nexport const RetryAfterError = /*       */ 0b1000000;\n\n// ...\n\n// Describes where we are in the React execution stack\nlet executionContext: ExecutionContext = NoContext;\n```\n\n注意: 和`lanes`的定义不同, `ExecutionContext`类型的变量, 在定义的时候采取的是 8 位二进制表示(因为变量的数量少, 8 位就够了, 没有必要写成 31 位).\n\n使用(由于使用的地方较多, 所以举一个[代表性强的例子](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619), `scheduleUpdateOnFiber` 函数是`react-reconciler`包对`react`包暴露出来的 api, 每一次更新都会调用, 所以比较特殊):\n\n```js\n// scheduleUpdateOnFiber函数中包含了好多关于executionContext的判断(都是使用位运算)\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber,\n  lane: Lane,\n  eventTime: number,\n) {\n  if (root === workInProgressRoot) {\n    // 判断: executionContext 不包含 RenderContext\n    if (\n      deferRenderPhaseUpdateToNextBatch ||\n      (executionContext & RenderContext) === NoContext\n    ) {\n      // ...\n    }\n  }\n  if (lane === SyncLane) {\n    if (\n      // 判断: executionContext 包含 LegacyUnbatchedContext\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      // 判断: executionContext 不包含 RenderContext或CommitContext\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      // ...\n    }\n  }\n  // ...\n}\n```\n\n## 总结\n\n本节介绍了位运算的基本使用, 并列举了位运算在`react`源码中的高频应用. 在特定的情况下, 使用位运算不仅是提高运算速度, 且位掩码能简洁和清晰的表示出二进制变量之间的关系. 二进制变量虽然有优势, 但是缺点也很明显, 不够直观, 扩展性不好(在 js 当中的二进制变量, 除去符号位, 最多只能使用 31 位, 当变量的数量超过 31 位就需要组合, 此时就会变得复杂). 在阅读源码时, 我们需要了解二级制变量和位掩码的使用. 但在实际开发中, 需要视情况而定, 不能盲目使用.\n\n## 参考资料\n\n[ECMAScript® Language Specification(Standard ECMA-262 5.1 Edition) Binary Bitwise Operators](https://www.ecma-international.org/ecma-262/5.1/#sec-11.10)\n\n[浮点数的二进制表示](https://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html)\n\n[IEEE 754](https://zh.wikipedia.org/wiki/IEEE_754)\n"
  },
  {
    "path": "docs/algorithm/dfs.md",
    "content": "---\ntitle: 深度优先遍历\norder: 2\n---\n\n# React 算法之深度优先遍历\n\n对于树或图结构的搜索(或遍历)来讲, 分为深度优先(DFS)和广度优先(BFS).\n\n## 概念\n\n深度优先遍历: DFS(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法.\n\n来自 wiki 上的解释(更权威): 当`节点v`的所在边都己被探寻过, 搜索将回溯到发现`节点v`的那条边的起始节点. 这一过程一直进行到已发现从源节点可达的所有节点为止. 如果还存在未被发现的节点, 则选择其中一个作为源节点并重复以上过程, 整个进程反复进行直到所有节点都被访问为止.\n\n## 实现方式\n\nDFS 的主流实现方式有 2 种.\n\n1. 递归(简单粗暴)\n2. 利用`栈`存储遍历路径\n\n```js\nfunction Node() {\n  this.name = '';\n  this.children = [];\n}\n\nfunction dfs(node) {\n  console.log('探寻阶段: ', node.name);\n  node.children.forEach((child) => {\n    dfs(child);\n  });\n  console.log('回溯阶段: ', node.name);\n}\n```\n\n2. 使用栈\n\n```js\nfunction Node() {\n  this.name = '';\n  this.children = [];\n\n  // 因为要分辨探寻阶段和回溯阶段, 所以必须要一个属性来记录是否已经访问过该节点\n  // 如果不打印探寻和回溯, 就不需要此属性\n  this.visited = false;\n}\n\nfunction dfs(node) {\n  const stack = [];\n  stack.push(node);\n  // 栈顶元素还存在, 就继续循环\n  while ((node = stack[stack.length - 1])) {\n    if (node.visited) {\n      console.log('回溯阶段: ', node.name);\n      // 回溯完成, 弹出该元素\n      stack.pop();\n    } else {\n      console.log('探寻阶段: ', node.name);\n      node.visited = true;\n      // 利用栈的先进后出的特性, 倒序将节点送入栈中\n      for (let i = node.children.length - 1; i >= 0; i--) {\n        stack.push(node.children[i]);\n      }\n    }\n  }\n}\n```\n\n## React 当中的使用场景\n\n深度优先遍历在`react`当中的使用非常典型, 最主要的使用时在`ReactElement`和`fiber`树的构造过程. 其次是在使用`context`时, 需要深度优先地查找消费`context`的节点.\n\n### ReactElement \"树\"的构造\n\n`ReactElement`不能算是严格的树结构, 为了方便表述, 后文都称之为树.\n\n在`react-reconciler`包中, `ReactElement`的构造过程实际上是嵌套在`fiber树构造循环`过程中的, 与`fiber`树的构造是相互交替进行的(在`fiber 树构建`章节中详细解读, 本节只介绍深度优先遍历的使用场景).\n\n`ReactElement`树的构造, 实际上就是各级组件`render`之后的总和. 整个过程体现在`reconciler`工作循环之中.\n\n源码位于[`ReactFiberWorkLoop.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1558)中, 此处为了简明, 已经将源码中与 dfs 无关的旁支逻辑去掉.\n\n```js\nfunction workLoopSync() {\n  // 1. 最外层循环, 保证每一个节点都能遍历, 不会遗漏\n  while (workInProgress !== null) {\n    performUnitOfWork(workInProgress);\n  }\n}\n\nfunction performUnitOfWork(unitOfWork: Fiber): void {\n  const current = unitOfWork.alternate;\n  let next;\n  // 2. beginWork是向下探寻阶段\n  next = beginWork(current, unitOfWork, subtreeRenderLanes);\n  if (next === null) {\n    // 3. completeUnitOfWork 是回溯阶段\n    completeUnitOfWork(unitOfWork);\n  } else {\n    workInProgress = next;\n  }\n}\n\nfunction completeUnitOfWork(unitOfWork: Fiber): void {\n  let completedWork = unitOfWork;\n  do {\n    const current = completedWork.alternate;\n    const returnFiber = completedWork.return;\n    let next;\n    // 3.1 回溯并处理节点\n    next = completeWork(current, completedWork, subtreeRenderLanes);\n    if (next !== null) {\n      // 判断在处理节点的过程中, 是否派生出新的节点\n      workInProgress = next;\n      return;\n    }\n    const siblingFiber = completedWork.sibling;\n    // 3.2 判断是否有旁支\n    if (siblingFiber !== null) {\n      workInProgress = siblingFiber;\n      return;\n    }\n    // 3.3 没有旁支 继续回溯\n    completedWork = returnFiber;\n    workInProgress = completedWork;\n  } while (completedWork !== null);\n}\n```\n\n以上源码本质上是采用递归的方式进行 dfs, 假设有以下组件结构:\n\n```js\nclass App extends React.Component {\n  render() {\n    return (\n      <div className=\"app\">\n        <header>header</header>\n        <Content />\n        <footer>footer</footer>\n      </div>\n    );\n  }\n}\n\nclass Content extends React.Component {\n  render() {\n    return (\n      <React.Fragment>\n        <p>1</p>\n        <p>2</p>\n        <p>3</p>\n      </React.Fragment>\n    );\n  }\n}\n\nexport default App;\n```\n\n则可以绘制出遍历路径如下:\n\n<img src=\"../../snapshots/dfs/dfs-reactelement.png\" width=\"500\"/>\n\n注意:\n\n- `ReactElement`树是在大循环中的`beginWork`阶段\"逐级\"生成的.\n- \"逐级\"中的每一级是指一个`class`或`function`类型的组件, 每调用一次`render`或执行一次`function`调用, 就会生成一批`ReactElement`节点.\n- `ReactElement`树的构造, 实际上就是各级组件`render`之后的总和.\n\n### fiber 树的构造\n\n在`ReactElement`的构造过程中, 同时伴随着`fiber`树的构造, `fiber`树同样也是在`beginWork`阶段生成的.\n\n绘制出遍历路径如下:\n\n![](../../snapshots/dfs/dfs-fibertree.png)\n\n### 查找 context 的消费节点\n\n当`context`改变之后, 需要找出依赖该`context`的所有子节点(详细分析会在`context原理`章节深入解读), 这里同样也是一个`DFS`, 具体源码在[ReactFiberNewContext.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L182-L295).\n\n将其主干逻辑剥离出来, 可以清晰的看出采用循环递归的方式进行遍历:\n\n```js\nexport function propagateContextChange(\n  workInProgress: Fiber,\n  context: ReactContext<mixed>,\n  changedBits: number,\n  renderLanes: Lanes,\n): void {\n  let fiber = workInProgress.child;\n  while (fiber !== null) {\n    let nextFiber;\n    // Visit this fiber.\n    const list = fiber.dependencies;\n    if (list !== null) {\n      // 匹配context等逻辑, 和dfs无关, 此处可以暂时忽略\n      // ...\n    } else {\n      // 向下探寻\n      nextFiber = fiber.child;\n    }\n    fiber = nextFiber;\n  }\n}\n```\n\n## 总结\n\n由于`react`内部使用了`ReactElement`和`fiber`两大树形结构, 所以有不少关于节点访问的逻辑.\n\n本节主要介绍了`DFS`的概念和它在`react`源码中的使用情况. 其中`fiber`树的`DFS`遍历, 涉及到的代码多, 分布广, 涵盖了`reconciler`阶段的大部分工作, 是`reconciler`阶段工作循环的核心流程.\n\n除了`DFS`之外, 源码中还有很多逻辑都是查找树中的节点(如: 向上查找父节点等). 对树形结构的遍历在源码中的比例很高, 了解这些算法技巧能够更好的理解`react`源码.\n\n## 参考资料\n\n[深度优先搜索](https://zh.wikipedia.org/wiki/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2)\n"
  },
  {
    "path": "docs/algorithm/diff.md",
    "content": "---\nnav:\n  title: 高频算法\n  order: 1\ntitle: 调和算法\norder: 0\n---\n\n# React 算法之调和算法\n\n## 概念\n\n调和函数([源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L1274-L1410))是在`fiber树构(对比更新)`过程中对`旧fiber节点`与`新reactElement`进行比较, 判定`旧fiber节点`是否可以复用的一个比较函数.\n\n调和函数仅是`fiber树构造`过程中的一个环节, 所以在深入理解这个函数之前, 建议对`fiber树构造`有一个宏观的理解(可以参考前文[fiber 树构造(初次创建)](../main/fibertree-create.md), [fiber 树构造(对比更新)](../main/fibertree-update.md)), 本节重点探讨其算法的实现细节.\n\n它的主要作用:\n\n1. 给新增,移动,和删除节点设置`fiber.flags`(新增, 移动: `Placement`, 删除: `Deletion`)\n2. 如果是需要删除的`fiber`, [除了自身打上`Deletion`之外, 还要将其添加到父节点的`effects`链表中](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L275-L294)(正常副作用队列的处理是在`completeWork`函数, 但是该节点(被删除)会脱离`fiber`树, 不会再进入`completeWork`阶段, 所以在`beginWork`阶段提前加入副作用队列).\n\n## 特性\n\n算法复杂度低, 从上至下比较整个树形结构, 时间复杂度被缩短到 O(n)\n\n## 基本原理\n\n1. 比较对象: `fiber`对象与`ReactElement`对象相比较.\n   - 注意: 此处有一个误区, 并不是两棵 fiber 树相比较, 而是`旧fiber`对象与`新ReactElement`对象向比较, 结果生成新的`fiber子节点`.\n   - 可以理解为输入`ReactElement`, 经过`reconcileChildren()`之后, 输出`fiber`.\n2. 比较方案:\n   - 单节点比较\n   - 可迭代节点比较\n\n### 单节点比较\n\n单节点的逻辑比较简明, 先直接看[源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L1135-L1233):\n\n```js\n// 只保留主干逻辑\nfunction reconcileSingleElement(\n  returnFiber: Fiber,\n  currentFirstChild: Fiber | null,\n  element: ReactElement,\n  lanes: Lanes,\n): Fiber {\n  const key = element.key;\n  let child = currentFirstChild;\n\n  while (child !== null) {\n    // currentFirstChild !== null, 表明是对比更新阶段\n    if (child.key === key) {\n      // 1. key相同, 进一步判断 child.elementType === element.type\n      switch (child.tag) {\n        // 只看核心逻辑\n        default: {\n          if (child.elementType === element.type) {\n            // 1.1 已经匹配上了, 如果有兄弟节点, 需要给兄弟节点打上Deletion标记\n            deleteRemainingChildren(returnFiber, child.sibling);\n            // 1.2 构造fiber节点, 新的fiber对象会复用current.stateNode, 即可复用DOM对象\n            const existing = useFiber(child, element.props);\n            existing.ref = coerceRef(returnFiber, child, element);\n            existing.return = returnFiber;\n            return existing;\n          }\n          break;\n        }\n      }\n      // Didn't match. 给当前节点点打上Deletion标记\n      deleteRemainingChildren(returnFiber, child);\n      break;\n    } else {\n      // 2. key不相同, 匹配失败, 给当前节点打上Deletion标记\n      deleteChild(returnFiber, child);\n    }\n    child = child.sibling;\n  }\n\n  {\n    // ...省略部分代码, 只看核心逻辑\n  }\n\n  // 新建节点\n  const created = createFiberFromElement(element, returnFiber.mode, lanes);\n  created.ref = coerceRef(returnFiber, currentFirstChild, element);\n  created.return = returnFiber;\n  return created;\n}\n```\n\n1. 如果是新增节点, 直接新建 fiber, 没有多余的逻辑\n2. 如果是对比更新\n   - 如果`key`和`type`都相同(即: `ReactElement.key` === `Fiber.key` 且 `Fiber.elementType === ReactElement.type`), 则复用\n   - 否则新建\n\n注意: 复用过程是调用`useFiber(child, element.props)`创建`新的fiber`对象, 这个`新fiber对象.stateNode = currentFirstChild.stateNode`, 即`stateNode`属性得到了复用, 故 DOM 节点得到了复用.\n\n### 可迭代节点比较(数组类型, [Symbol.iterator]=fn,[@@iterator]=fn)\n\n可迭代节点比较, 在[源码中](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L1346-L1362)被分为了 2 个部分:\n\n```js\nfunction reconcileChildFibers(\n  returnFiber: Fiber,\n  currentFirstChild: Fiber | null,\n  newChild: any,\n  lanes: Lanes,\n): Fiber | null {\n  if (isArray(newChild)) {\n    return reconcileChildrenArray(\n      returnFiber,\n      currentFirstChild,\n      newChild,\n      lanes,\n    );\n  }\n  if (getIteratorFn(newChild)) {\n    return reconcileChildrenIterator(\n      returnFiber,\n      currentFirstChild,\n      newChild,\n      lanes,\n    );\n  }\n}\n```\n\n其中`reconcileChildrenArray函数`(针对数组类型)和`reconcileChildrenIterator`(针对可迭代类型)的核心逻辑几乎一致, 下文将分析[`reconcileChildrenArray()`函数](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L771-L924). 如果是新增节点, 所有的比较逻辑都无法命中, 只有`对比更新`过程, 才有实际作用, 所以下文重点分析`对比更新`的情况.\n\n```js\nfunction reconcileChildrenArray(\n  returnFiber: Fiber,\n  currentFirstChild: Fiber | null,\n  newChildren: Array<*>,\n  lanes: Lanes,\n): Fiber | null {\n  let resultingFirstChild: Fiber | null = null;\n  let previousNewFiber: Fiber | null = null;\n\n  let oldFiber = currentFirstChild;\n  let lastPlacedIndex = 0;\n  let newIdx = 0;\n  let nextOldFiber = null;\n  // 1. 第一次循环: 遍历最长公共序列(key相同), 公共序列的节点都视为可复用\n  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {\n    // 后文分析\n  }\n\n  if (newIdx === newChildren.length) {\n    // 如果newChildren序列被遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)\n    deleteRemainingChildren(returnFiber, oldFiber);\n    return resultingFirstChild;\n  }\n\n  if (oldFiber === null) {\n    // 如果oldFiber序列被遍历完, 那么newChildren序列中剩余节点都视为新增(打上Placement标记)\n    for (; newIdx < newChildren.length; newIdx++) {\n      // 后文分析\n    }\n    return resultingFirstChild;\n  }\n\n  // ==================分割线==================\n  const existingChildren = mapRemainingChildren(returnFiber, oldFiber);\n\n  // 2. 第二次循环: 遍历剩余非公共序列, 优先复用oldFiber序列中的节点\n  for (; newIdx < newChildren.length; newIdx++) {}\n\n  if (shouldTrackSideEffects) {\n    // newChildren已经遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)\n    existingChildren.forEach((child) => deleteChild(returnFiber, child));\n  }\n\n  return resultingFirstChild;\n}\n```\n\n`reconcileChildrenArray`函数源码看似很长, 梳理其主干之后, 其实非常清晰.\n\n通过形参, 首先明确比较对象是`currentFirstChild: Fiber | null`和`newChildren: Array<*>`:\n\n- `currentFirstChild`: 是一个`fiber`节点, 通过`fiber.sibling`可以将兄弟节点全部遍历出来. 所以可以将`currentFirstChild`理解为链表头部, 它代表一个序列, 源码中被记为`oldFiber`.\n- `newChildren`: 是一个数组, 其中包含了若干个`ReactElement`对象. 所以`newChildren`也代表一个序列.\n\n所以`reconcileChildrenArray`实际就是 2 个序列之间的比较(`链表oldFiber`和`数组newChildren`), 最后返回合理的`fiber`序列.\n\n上述代码中, 以注释分割线为界限, 整个核心逻辑分为 2 步骤:\n\n1. 第一次循环: 遍历最长`公共`序列(key 相同), 公共序列的节点都视为可复用\n   - 如果`newChildren序列`被遍历完, 那么`oldFiber序列`中剩余节点都视为删除(打上`Deletion`标记)\n   - 如果`oldFiber序列`被遍历完, 那么`newChildren序列`中剩余节点都视为新增(打上`Placement`标记)\n2. 第二次循环: 遍历剩余`非公共`序列, 优先复用 oldFiber 序列中的节点\n   - 在对比更新阶段(非初次创建`fiber`, 此时`shouldTrackSideEffects`被设置为 true). 第二次循环遍历完成之后, `oldFiber序列中`没有匹配上的节点都视为删除(打上`Deletion`标记)\n\n假设有如下图所示 2 个初始化序列:\n\n![](../../snapshots/diff/before-traverse.png)\n\n接下来第一次循环, 会遍历公共序列`A,B`, 生成的 fiber 节点`fiber(A), fiber(B)`可以复用.\n\n![](../../snapshots/diff/traverse1.png)\n\n最后第二次循环, 会遍历剩余序列`E,C,X,Y`:\n\n- 生成的 fiber 节点`fiber(E), fiber(C)`可以复用. 其中`fiber(C)`节点发生了位移(打上`Placement`标记).\n- `fiber(X), fiber(Y)`是新增(打上`Placement`标记).\n- 同时`oldFiber`序列中的`fiber(D)`节点确定被删除(打上`Deletion`标记).\n\n![](../../snapshots/diff/traverse2.png)\n\n整个主干逻辑就介绍完了, 接下来贴上完整源码\n\n> 第一次循环\n\n```js\n// 1. 第一次循环: 遍历最长公共序列(key相同), 公共序列的节点都视为可复用\nfor (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {\n  if (oldFiber.index > newIdx) {\n    nextOldFiber = oldFiber;\n    oldFiber = null;\n  } else {\n    nextOldFiber = oldFiber.sibling;\n  }\n  // new槽位和old槽位进行比较, 如果key不同, 返回null\n  // key相同, 比较type是否一致. type一致则执行useFiber(update逻辑), type不一致则运行createXXX(insert逻辑)\n  const newFiber = updateSlot(\n    returnFiber,\n    oldFiber,\n    newChildren[newIdx],\n    lanes,\n  );\n\n  if (newFiber === null) {\n    // 如果返回null, 表明key不同. 无法满足公共序列条件, 退出循环\n    if (oldFiber === null) {\n      oldFiber = nextOldFiber;\n    }\n    break;\n  }\n  if (shouldTrackSideEffects) {\n    // 若是新增节点, 则给老节点打上Deletion标记\n    if (oldFiber && newFiber.alternate === null) {\n      deleteChild(returnFiber, oldFiber);\n    }\n  }\n\n  // lastPlacedIndex 记录被移动的节点索引\n  // 如果当前节点可复用, 则要判断位置是否移动.\n  lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);\n\n  // 更新resultingFirstChild结果序列\n  if (previousNewFiber === null) {\n    resultingFirstChild = newFiber;\n  } else {\n    previousNewFiber.sibling = newFiber;\n  }\n  previousNewFiber = newFiber;\n  oldFiber = nextOldFiber;\n}\n```\n\n> 第二次循环\n\n```js\n// 1. 将第一次循环后, oldFiber剩余序列加入到一个map中. 目的是为了第二次循环能顺利的找到可复用节点\nconst existingChildren = mapRemainingChildren(returnFiber, oldFiber);\n\n// 2. 第二次循环: 遍历剩余非公共序列, 优先复用oldFiber序列中的节点\nfor (; newIdx < newChildren.length; newIdx++) {\n  const newFiber = updateFromMap(\n    existingChildren,\n    returnFiber,\n    newIdx,\n    newChildren[newIdx],\n    lanes,\n  );\n  if (newFiber !== null) {\n    if (shouldTrackSideEffects) {\n      if (newFiber.alternate !== null) {\n        // 如果newFiber是通过复用创建的, 则清理map中对应的老节点\n        existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);\n      }\n    }\n    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);\n    // 更新resultingFirstChild结果序列\n    if (previousNewFiber === null) {\n      resultingFirstChild = newFiber;\n    } else {\n      previousNewFiber.sibling = newFiber;\n    }\n    previousNewFiber = newFiber;\n  }\n}\n// 3. 善后工作, 第二次循环完成之后, existingChildren中剩余的fiber节点就是将要被删除的节点, 打上Deletion标记\nif (shouldTrackSideEffects) {\n  existingChildren.forEach((child) => deleteChild(returnFiber, child));\n}\n```\n\n### 结果\n\n无论是单节点还是可迭代节点的比较, 最终的目的都是生成下级子节点. 并在`reconcileChildren`过程中, 给一些有副作用的节点(新增, 删除, 移动位置等)打上副作用标记, 等待 commit 阶段(参考[fiber 树渲染](../main/commit.md))的处理.\n\n## 总结\n\n本节介绍了 React 源码中, `fiber构造循环`阶段用于生成下级子节点的`reconcileChildren`函数(函数中的算法被称为调和算法), 并演示了`可迭代节点比较`的图解示例. 该算法十分巧妙, 其核心逻辑把`newChildren序列`分为 2 步遍历, 先遍历公共序列, 再遍历非公共部分, 同时复用`oldFiber`序列中的节点.\n"
  },
  {
    "path": "docs/algorithm/heapsort.md",
    "content": "---\ntitle: 堆排序\norder: 3\n---\n\n# React 算法之堆排序\n\n## 概念\n\n二叉堆是一种特殊的堆, 二叉堆是[完全二叉树](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8F%89%E6%A0%91#%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)或者近似完全二叉树.\n\n堆排序是利用二叉堆的特性, 对根节点(最大或最小)进行循环提取, 从而达到排序目的(堆排序本质上是一种选择排序), 时间复杂度为`O(nlog n)`.\n\n## 特性\n\n1. 父节点的值>=子节点的值(最大堆), 父节点的值<=子节点的值(最小堆). 每个节点的左子树和右子树都是一个二叉堆.\n2. 假设一个数组`[k0, k1, k2, ...kn]`下标从 0 开始. 则`ki <= k2i+1,ki <= k2i+2` 或者 `ki >= k2i+1,ki >= k2i+2` (i = 0,1,2,3 .. n/2)\n\n## 基本使用\n\n假设现在有一个乱序数组, [5,8,0,10,4,6,1], 现在将其构造成一个最小堆\n\n1. 构造二叉堆\n   - 需要从最后一个非叶子节点开始, 向下调整堆结构\n\n![](../../snapshots/data-structure/minheap.png)\n\n2. 插入节点, 重新向上调整堆(`sift-up`)\n   - 将新元素插入到数组末尾之后, 要重新调整数组结构, 保证数组任然是最小(或最大)堆.\n\n![](../../snapshots/data-structure/minheap-insert.png)\n\n3. 提取或删除根节点(顶端节点), 重新向下调整堆(`sift-down`)\n   - 对于最大堆, 提取的是最大值. 对于最小堆, 提取的是最小值.\n   - 顶点被提取之后, 要重新调整数组结构, 保证数组任然是最小(或最大)堆.\n\n![](../../snapshots/data-structure/minheap-remove.png)\n\n4. 排序过程\n\n利用二叉堆的特性, 排序就是循环提取根节点的过程. 循环执行步骤 3, 直到将所有的节点都提取完成, 被提取的节点构成的数组就是一个有序数组.\n\n注意:\n\n- 如需升序排序, 应该构造最大堆. 因为最大的元素最先被提取出来, 被放置到了数组的最后, 最终数组中最后一个元素为最大元素.\n- 如需降序排序, 应该构造最小堆. 因为最小的元素最先被提取出来, 被放置到了数组的最后, 最终数组中最后一个元素为最小元素.\n- 堆排序是一种不稳定排序(对于相同大小的元素, 在排序之后有可能和排序前的先后次序被打乱).\n\n## 代码演示\n\n将乱序数组`[5,8,0,10,4,6,1]`降序排列\n\n步骤:\n\n1. 构造最小堆\n2. 循环提取根节点, 直到全部提取完\n\n```js\nconst minHeapSort = (arr) => {\n  // 1. 构造最小堆\n  buildMinHeap(arr);\n  // 2. 循环提取根节点arr[0], 直到全部提取完\n  for (let i = arr.length - 1; i > 0; i--) {\n    let tmp = arr[0];\n    arr[0] = arr[i];\n    arr[i] = tmp;\n    siftDown(arr, 0, i - 1);\n  }\n};\n\n// 把整个数组构造成最小堆\nconst buildMinHeap = (arr) => {\n  if (arr.length < 2) {\n    return arr;\n  }\n  const startIndex = Math.floor(arr.length / 2 - 1);\n  for (let i = startIndex; i >= 0; i--) {\n    siftDown(arr, i, arr.length - 1);\n  }\n};\n\n// 从startIndex索引开始, 向下调整最小堆\nconst siftDown = (arr, startIndex, endIndex) => {\n  const leftChildIndx = 2 * startIndex + 1;\n  const rightChildIndx = 2 * startIndex + 2;\n  let swapIndex = startIndex;\n  let tmpNode = arr[startIndex];\n  if (leftChildIndx <= endIndex) {\n    if (arr[leftChildIndx] < tmpNode) {\n      // 待定是否交换, 因为right子节点有可能更小\n      tmpNode = arr[leftChildIndx];\n      swapIndex = leftChildIndx;\n    }\n  }\n  if (rightChildIndx <= endIndex) {\n    if (arr[rightChildIndx] < tmpNode) {\n      // 比left节点更小, 替换swapIndex\n      tmpNode = arr[rightChildIndx];\n      swapIndex = rightChildIndx;\n    }\n  }\n  if (swapIndex !== startIndex) {\n    // 1.交换节点\n    arr[swapIndex] = arr[startIndex];\n    arr[startIndex] = tmpNode;\n\n    // 2. 递归调用, 继续向下调整\n    siftDown(arr, swapIndex, endIndex);\n  }\n};\n```\n\n测试:\n\n```js\nvar arr1 = [5, 8, 0, 10, 4, 6, 1];\nminHeapSort(arr1);\nconsole.log(arr1); // [10, 8, 6, 5,4, 1, 0]\n\nvar arr2 = [5];\nminHeapSort(arr2);\nconsole.log(arr2); // [ 5 ]\n\nvar arr3 = [5, 1];\nminHeapSort(arr3);\nconsole.log(arr3); //[ 5, 1 ]\n```\n\n## React 当中的使用场景\n\n对于二叉堆的应用是在`scheduler`包中, 有 2 个数组[`taskQueue`和`timerQueue`](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js#L61-L63), 它们都是以`最小堆`的形式进行存储, 这样就能保证以`O(1)`的时间复杂度, 取到数组顶端的对象(优先级最高的 task).\n\n具体的调用过程被封装到了[`SchedulerMinHeap.js`](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/SchedulerMinHeap.js#L41-L87), 其中有 2 个函数`siftUp`,`siftDown`分别对应向上调整和向下调整.\n\n```js\ntype Heap = Array<Node>;\ntype Node = {|\n  id: number,\n  sortIndex: number,\n|};\n\n// 添加新节点, 添加之后, 需要调用`siftUp`函数向上调整堆.\nexport function push(heap: Heap, node: Node): void {\n  const index = heap.length;\n  heap.push(node);\n  siftUp(heap, node, index);\n}\n\n// 查看堆的顶点, 也就是优先级最高的`task`或`timer`\nexport function peek(heap: Heap): Node | null {\n  const first = heap[0];\n  return first === undefined ? null : first;\n}\n\n// 将堆的顶点提取出来, 并删除顶点之后, 需要调用`siftDown`函数向下调整堆.\nexport function pop(heap: Heap): Node | null {\n  const first = heap[0];\n  if (first !== undefined) {\n    const last = heap.pop();\n    if (last !== first) {\n      heap[0] = last;\n      siftDown(heap, last, 0);\n    }\n    return first;\n  } else {\n    return null;\n  }\n}\n\n// 当插入节点之后, 需要向上调整堆结构, 保证数组是一个最小堆.\nfunction siftUp(heap, node, i) {\n  let index = i;\n  while (true) {\n    const parentIndex = (index - 1) >>> 1;\n    const parent = heap[parentIndex];\n    if (parent !== undefined && compare(parent, node) > 0) {\n      // The parent is larger. Swap positions.\n      heap[parentIndex] = node;\n      heap[index] = parent;\n      index = parentIndex;\n    } else {\n      // The parent is smaller. Exit.\n      return;\n    }\n  }\n}\n\n// 向下调整堆结构, 保证数组是一个最小堆.\nfunction siftDown(heap, node, i) {\n  let index = i;\n  const length = heap.length;\n  while (index < length) {\n    const leftIndex = (index + 1) * 2 - 1;\n    const left = heap[leftIndex];\n    const rightIndex = leftIndex + 1;\n    const right = heap[rightIndex];\n\n    // If the left or right node is smaller, swap with the smaller of those.\n    if (left !== undefined && compare(left, node) < 0) {\n      if (right !== undefined && compare(right, left) < 0) {\n        heap[index] = right;\n        heap[rightIndex] = node;\n        index = rightIndex;\n      } else {\n        heap[index] = left;\n        heap[leftIndex] = node;\n        index = leftIndex;\n      }\n    } else if (right !== undefined && compare(right, node) < 0) {\n      heap[index] = right;\n      heap[rightIndex] = node;\n      index = rightIndex;\n    } else {\n      // Neither child is smaller. Exit.\n      return;\n    }\n  }\n}\n```\n\n- `peek`函数: 查看堆的顶点, 也就是优先级最高的`task`或`timer`.\n- `pop`函数: 将堆的顶点提取出来, 并删除顶点之后, 需要调用`siftDown`函数向下调整堆.\n- `push`函数: 添加新节点, 添加之后, 需要调用`siftUp`函数向上调整堆.\n- `siftDown`函数: 向下调整堆结构, 保证数组是一个最小堆.\n- `siftUp`函数: 当插入节点之后, 需要向上调整堆结构, 保证数组是一个最小堆.\n\n## 总结\n\n本节介绍了`堆排序`的基本使用, 并说明了`堆排序`在`react`源码中的应用. 在阅读`scheduler`包的源码时, 会更加清晰的理解作者的思路.\n"
  },
  {
    "path": "docs/algorithm/linkedlist.md",
    "content": "---\ntitle: 链表操作\norder: 4\n---\n\n# React 算法之链表操作\n\n## 概念\n\n来自 wiki 上的解释: 链表（Linked list）是一种常见的基础数据结构, 是一种线性表, 但是并不会按线性的顺序存储数据, 而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储，链表在插入的时候可以达到 O(1)的复杂度, 但是查找一个节点或者访问特定编号的节点则需要 O(n)的时间.\n\n1. 单向链表: 每个节点包含两个域, 一个信息域和一个指针域. 这个指针指向列表中的下一个节点, 而最后一个节点则指向一个空值.\n2. 双向链表: 每个节点有两个连接, 一个指向前一个节点(第一个节点指向空值), 而另一个指向下一个节点(最后一个节点指向空值).\n3. 循环链表: 在单向链表的基础上, 首节点和末节点被连接在一起.\n\n![](../../snapshots/linkedlist/summary.png)\n\n## 基本使用\n\n1. 节点插入, 时间复杂度`O(1)`\n2. 节点查找, 时间复杂度`O(n)`\n3. 节点删除, 时间复杂度`O(1)`\n4. 反转链表, 时间复杂度`O(n)`\n\n```js\n// 定义Node节点类型\nfunction Node(name) {\n  this.name = name;\n  this.next = null;\n}\n\n// 链表\nfunction LinkedList() {\n  this.head = new Node('head');\n\n  // 查找node节点的前一个节点\n  this.findPrevious = function (node) {\n    let currentNode = this.head;\n    while (currentNode && currentNode.next !== node) {\n      currentNode = currentNode.next;\n    }\n    return currentNode;\n  };\n\n  // 在node后插入新节点newElement\n  this.insert = function (name, node) {\n    const newNode = new Node(name);\n    newNode.next = node.next;\n    node.next = newNode;\n  };\n\n  // 删除节点\n  this.remove = function (node) {\n    const previousNode = this.findPrevious(node);\n    if (previousNode) {\n      previousNode.next = node.next;\n    }\n  };\n\n  // 反转链表\n  this.reverse = function () {\n    let prev = null;\n    let current = this.head;\n    while (current) {\n      const tempNode = current.next;\n      // 重新设置next指针, 使其指向前一个节点\n      current.next = prev;\n      // 游标后移\n      prev = current;\n      current = tempNode;\n    }\n    // 重新设置head节点\n    this.head = prev;\n  };\n}\n```\n\n## React 当中的使用场景\n\n在 react 中, 链表的使用非常高频, 主要集中在`fiber`和`hook`对象的属性中.\n\n### fiber 对象\n\n在[react 高频对象](../main/object-structure.md#Fiber)中对`fiber`对象的属性做了说明, 这里列举出 4 个链表属性.\n\n1. `effect`链表(链式队列): 存储有副作用的子节点, 构成该队列的元素是`fiber`对象\n\n   - `fiber.nextEffect`: 单向链表, 指向下一个有副作用的 fiber 节点.\n   - `fiber.firstEffect`: 指向副作用链表中的第一个 fiber 节点.\n   - `fiber.lastEffect`: 指向副作用链表中的最后一个 fiber 节点.\n\n   <img src=\"../../snapshots/linkedlist/effects.png\" width=\"600\">\n\n   注意: 此处只表示出链表的结构示意图, 在`fiber 树构造`章节中会对上图的结构进行详细解读.\n\n2. `updateQueue`链表(链式队列): 存储将要更新的状态, 构成该队列的元素是`update`对象\n\n   - `fiber.updateQueue.pending`: 存储`state`更新的队列(链式队列), `class`类型节点的`state`改动之后, 都会创建一个`update`对象添加到这个队列中. 由于此队列是一个环形队列, 为了方便添加新元素和快速拿到队首元素, 所以`pending`指针指向了队列中最后一个元素.\n\n   ![](../../snapshots/data-structure/updatequeue.png)\n\n   注意: 此处只表示出链表的结构示意图, 在`状态组件(class 与 function)`章节中会对上图的结构进行详细解读.\n\n### Hook 对象\n\n在[react 高频对象](../main/object-structure.md#Hook)中对`Hook`对象的属性做了说明, `Hook`对象具备`.next`属性, 所以`Hook`对象本身就是链表中的一个节点.\n\n此外`hook.queue.pending`也构成了一个链表, 将`hook`链表与`hook.queue.pending`链表同时表示在图中, 得到的结构如下:\n\n![](../../snapshots/data-structure/fiber-hook.png)\n\n注意: 此处只表示出链表的结构示意图, 在`hook 原理`章节中会对上图的结构进行详细解读.\n\n### 链表合并\n\n在`react`中, 发起更新之后, 会通过`链表合并`的方式把等待(`pending`状态)更新的队列(`updateQueue`)合并到基础队列(`class`组件:`fiber.updateQueue.firstBaseUpdate`;`function`组件: `hook.baseQueue`), 最后通过遍历`baseQueue`筛选出优先级足够的`update`对象, 组合成最终的组件状态(`state`). 这个过程发生在`reconciler`阶段, 分别涉及到`class`组件和`function`组件.\n\n具体场景:\n\n1. `class`组件中\n\n   - 在`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)).\n\n     ```js\n     export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {\n       const updateQueue = fiber.updateQueue;\n       // ...\n       const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;\n       // 将新的update对象添加到fiber.updateQueue.shared.pending链表上\n       const pending = sharedQueue.pending;\n       if (pending === null) {\n         update.next = update;\n       } else {\n         update.next = pending.next;\n         pending.next = update;\n       }\n       sharedQueue.pending = update;\n     }\n     ```\n\n     由于`fiber.updateQueue.shared.pending`是一个环形链表, 所以`fiber.updateQueue.shared.pending`永远指向末尾元素(保证快速添加新元素)\n\n     ![](../../snapshots/linkedlist/fiber.updatequeue.png)\n\n   - 在`fiber`树构建阶段(或`reconciler`阶段), 会把`fiber.updateQueue.shared.pending`合并到`fiber.updateQueue.firstBaseUpdate`队列上([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactUpdateQueue.old.js#L394-L572)).\n\n     ```js\n     export function processUpdateQueue<State>(\n       workInProgress: Fiber,\n       props: any,\n       instance: any,\n       renderLanes: Lanes,\n     ): void {\n       // This is always non-null on a ClassComponent or HostRoot\n       const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);\n       let firstBaseUpdate = queue.firstBaseUpdate;\n       let lastBaseUpdate = queue.lastBaseUpdate;\n       // Check if there are pending updates. If so, transfer them to the base queue.\n       let pendingQueue = queue.shared.pending;\n       if (pendingQueue !== null) {\n         queue.shared.pending = null;\n         // The pending queue is circular. Disconnect the pointer between first\n         // and last so that it's non-circular.\n         const lastPendingUpdate = pendingQueue;\n         const firstPendingUpdate = lastPendingUpdate.next;\n         lastPendingUpdate.next = null;\n         // Append pending updates to base queue\n         if (lastBaseUpdate === null) {\n           firstBaseUpdate = firstPendingUpdate;\n         } else {\n           lastBaseUpdate.next = firstPendingUpdate;\n         }\n         lastBaseUpdate = lastPendingUpdate;\n       }\n     }\n     ```\n\n     ![](../../snapshots/linkedlist/fiber.updatequeue-merge-before.png)\n\n     ![](../../snapshots/linkedlist/fiber.updatequeue-merge-after.png)\n\n2. `function`组件中\n\n   - 在`function`组件中使用`Hook`对象(`useState`), 并改变`Hook`对象的值(内部会调用`dispatchAction`), 此时也会创建`update(hook)`对象并添加到`hook.queue.pending`链式队列([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1645-L1682)).\n   - `hook.queue.pending`也是一个环形链表(与`fiber.updateQueue.shared.pending`的结构很相似)\n\n     ```js\n     function dispatchAction<S, A>(\n       fiber: Fiber,\n       queue: UpdateQueue<S, A>,\n       action: A,\n     ) {\n       // ... 省略部分代码\n       const pending = queue.pending;\n       if (pending === null) {\n         // This is the first update. Create a circular list.\n         update.next = update;\n       } else {\n         update.next = pending.next;\n         pending.next = update;\n       }\n       queue.pending = update;\n     }\n     ```\n\n   - 在`fiber`树构建阶段(或`reconciler`阶段), 会将`hook.queue.pending`合并到`hook.baseQueue`队列上([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L672-L694)).\n\n     ```js\n     function updateReducer<S, I, A>(\n       reducer: (S, A) => S,\n       initialArg: I,\n       init?: (I) => S,\n     ): [S, Dispatch<A>] {\n       // ... 省略部分代码\n       if (pendingQueue !== null) {\n         if (baseQueue !== null) {\n           // 在这里进行队列的合并\n           const baseFirst = baseQueue.next;\n           const pendingFirst = pendingQueue.next;\n           baseQueue.next = pendingFirst;\n           pendingQueue.next = baseFirst;\n         }\n         current.baseQueue = baseQueue = pendingQueue;\n         queue.pending = null;\n       }\n     }\n     ```\n\n     ![](../../snapshots/linkedlist/hook.baseQueue-merge-before.png)\n\n     ![](../../snapshots/linkedlist/hook.baseQueue-merge-after.png)\n\n## 总结\n\n本节主要介绍了`链表`的概念和它在`react`源码中的使用情况. `react`中主要的数据结构都和链表有关, 使用非常高频. 源码中`链表合并`, `环形链表拆解`, `链表遍历`的代码篇幅很多, 所以深入理解链表的使用, 对理解`react原理`大有益处.\n\n## 参考资料\n\n- [链表](https://zh.wikipedia.org/wiki/%E9%93%BE%E8%A1%A8)\n"
  },
  {
    "path": "docs/algorithm/stack.md",
    "content": "---\ntitle: 栈操作\norder: 5\n---\n\n# React 算法之栈操作\n\n## 概念\n\n来自 wiki 上的解释: `堆栈`(`stack`)又称为`栈`或`堆叠`, 是计算机科学中的一种抽象资料类型, 只允许在有序的线性资料集合的一端(称为堆栈顶端`top`)进行加入数据(`push`)和移除数据(`pop`)的运算. 因而按照后进先出(`LIFO, Last In First Out`)的原理运作.\n\n注意:\n\n- `栈`(stack)又叫做`堆栈`, 这里特指数据结构中的`栈`(另一种`程序内存分配`中的栈, 本系列不做介绍, 读者可自行了解).\n- `堆栈`中虽带有一个`堆`字, 只是命名, 不要和`堆`混淆.\n- 常说的`堆`有 2 种指代, 一种是`数据结构`中的堆(在[React 算法之堆排序](./heapsort.md)中有介绍), 另一种是`程序内存分配`中的堆(本系列不做介绍, 读者可自行了解).\n\n## 特性\n\n1. 先入后出, 后入先出.\n2. 除头尾节点之外, 每个元素有一个前驱, 一个后继.\n\n## 基本使用\n\n1. 压栈: `push()`\n2. 弹栈: `pop((`\n3. 预览栈顶元素: `peek()`\n\n```js\nclass Stack {\n  constructor() {\n    this.dataStore = [];\n    this.top = 0;\n  }\n\n  // 压栈\n  push(element) {\n    this.dataStore[this.top++] = element;\n  }\n\n  // 弹栈\n  pop() {\n    return this.dataStore[--this.top];\n  }\n\n  // 预览栈顶元素\n  peek() {\n    return this.dataStore[this.top - 1];\n  }\n\n  // 检测栈内存储了多少个元素\n  length() {\n    return this.top;\n  }\n\n  // 清空栈\n  clear() {\n    this.top = 0;\n  }\n}\n```\n\n测试代码:\n\n```js\nconst test = () => {\n  const stack = new Stack();\n  console.log('压栈a: ');\n  stack.push('a');\n  console.log('压栈b: ');\n  stack.push('b');\n  console.log('压栈c: ');\n  stack.push('c');\n  console.log('栈高度: ', stack.length());\n  console.log('栈顶元素: ', stack.peek());\n  console.log('弹出: ', stack.pop());\n  console.log('栈顶元素: ', stack.peek());\n  console.log('压栈d: ');\n  stack.push('d');\n  console.log('栈顶元素: ', stack.peek());\n  console.log('清空栈: ');\n  stack.clear();\n  console.log('栈高度: ', stack.length());\n  console.log('压栈e: ');\n  stack.push('e');\n  console.log('栈顶元素: ', stack.peek());\n};\n```\n\n利用栈先进后出的特性, 在实际编码中应用非常广泛. 如`回溯`,`递归`,`深度优先搜索`等经典算法都可以利用栈的特性来实现. 由于本文的目的是讲解栈`react`中的使用场景, 所以与栈相关的经典案例本文不再列举, 请读者移步其他算法资料.\n\n## React 当中的使用场景\n\n### Context 状态管理 {#context}\n\n在`fiber`树创建过程中, 如果使用了[`Context api`](https://zh-hans.reactjs.org/docs/context.html#reactcreatecontext)(具体来说是使用`Context.Provider`, `Class.contextType`, `Context.Consumer`等`api`), `react`内部会维护一个`栈`来保存提供者(`Context.Provider`)的状态, 供给消费者(`Context.Consumer`)使用.\n\n首先看`stack`的定义([ReactFiberStack.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberStack.old.js#L10-L71)中):\n\n```js\nexport type StackCursor<T> = {| current: T |};\n\n// 维护一个全局stack\nconst valueStack: Array<any> = [];\nlet index = -1;\n\n// 一个工厂函数, 创建StackCursor对象\nfunction createCursor<T>(defaultValue: T): StackCursor<T> {\n  return {\n    current: defaultValue,\n  };\n}\nfunction isEmpty(): boolean {\n  return index === -1;\n}\n// 出栈\nfunction pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {\n  if (index < 0) {\n    return;\n  }\n  cursor.current = valueStack[index];\n  valueStack[index] = null;\n  index--;\n}\n// 入栈\nfunction push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {\n  index++;\n  // 注意: 这里存储的是 cursor当前值, 随后更新了cursor.current为\n  valueStack[index] = cursor.current;\n  cursor.current = value;\n}\n```\n\n在`ReactFiberStack.js`源码中, 定义的`valueStack`作为全局变量, 用来存储所有的`StackCursor.current`(不仅仅存储`context api`相关的`StackCursor`, 在`context 原理`章节中详细解读, 本节只讨论与`context api`相关的栈操作).\n\n注意`StackCursor`是一个泛型对象, 与`context api`相关的`StackCursor`定义在[`ReactFiberNewContext.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L38):\n\n```js\n// 定义全局 valueCursor, 用于管理<Context.Provider/>组件的value\nconst valueCursor: StackCursor<mixed> = createCursor(null);\n\n// ...省略无关代码\n\n// 将context当前的值保存到valueCursor中, 并设置context._currentValue为最新值\n// 运行完成之后context为最新状态\nexport function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {\n  const context: ReactContext<T> = providerFiber.type._context;\n  push(valueCursor, context._currentValue, providerFiber);\n  context._currentValue = nextValue;\n}\n\n// 取出valueCursor中保存的旧值, 设置到context._currentValue上.\n// 运行完成之后context恢复到上一个状态\nexport function popProvider(providerFiber: Fiber): void {\n  const currentValue = valueCursor.current;\n  pop(valueCursor, providerFiber);\n  const context: ReactContext<any> = providerFiber.type._context;\n  context._currentValue = currentValue;\n}\n```\n\n假设有如下组件结构(平时开发很难有这样的代码, 此处完全是为了演示`context api`中涉及到的栈操作):\n\n```js\nconst MyContext = React.createContext(0);\n\nexport default function App() {\n  return (\n    // 第一级\n    <MyContext.Provider value={1}>\n      <MyContext.Consumer>\n        {(value1) => (\n          //第二级嵌套\n          <MyContext.Provider value={2}>\n            <MyContext.Consumer>\n              {(value2) => (\n                // 第三级嵌套\n                <MyContext.Provider value={3}>\n                  <MyContext.Consumer>\n                    {(value3) => (\n                      <span>\n                        {value1}-{value2}-{value3}\n                      </span>\n                    )}\n                  </MyContext.Consumer>\n                </MyContext.Provider>\n              )}\n            </MyContext.Consumer>\n          </MyContext.Provider>\n        )}\n      </MyContext.Consumer>\n    </MyContext.Provider>\n  );\n}\n```\n\n可在`codesandbox`中查看[运行结果](https://codesandbox.io/s/inspiring-wildflower-lkpom?file=/src/App.js).\n\n将`fiber`树构造过程中`MyContext`对象在栈中的变化情况表示出来:\n\n1. `beginWork`阶段: 入栈\n\n   - `reconciler`之前, 由于`const MyContext = React.createContext(0);`已经创建了`MyContext`对象, 所以其初始值是`0`.\n   - `reconciler`过程中, 每当遇到`Context.Provider`类型的节点, 则会执行`pushProvider`.\n\n![](../../snapshots/stack/context-beginwork.png)\n\n2. `completeWork`阶段: 出栈\n   - `reconciler`过程中, 每当遇到`Context.Provider`类型的节点, 则会执行`popProvider`.\n   - `reconciler`之后, `valueStack`和`valueCursor`以及`MyContext`都恢复到了初始状态.\n\n![](../../snapshots/stack/context-completework.png)\n\n注意:\n\n- 本节只分析`context`实现源码中与`栈`相关的部分, 所以只涉及到了`Context.Provider`(供应者)节点.\n- 对于`Context.Consumer`(消费者)以及更新阶段`context`的运行机制的深入解读放在`context原理`章节中.\n\n### executionContext 执行上下文\n\n`executionContext`是在`ReactFiberWorkLoop.js`中定义的一个[全局变量(相对于该闭包)](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L247-L256), 且定义成二进制变量, 通过位运算来维护其状态(在[React 算法之位运算](./bitfield.md)一文中已有介绍).\n\n表面上看`executionContext`和栈并没有直接关系, 但实际在改变`executionContext`的时候, 巧妙的利用了`函数调用栈`, 实现`executionContext`状态的维护.\n\n本节主要是体现`executionContext`和`函数调用栈`之间的配合运用([具体源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1117-L1207)), 这里以`batchedUpdates`和`unbatchedUpdates`为例进行分析.\n\n```js\nexport function batchedUpdates<A, R>(fn: (A) => R, a: A): R {\n  // 在执行回调之前, 先改变 executionContext\n  const prevExecutionContext = executionContext;\n  executionContext |= BatchedContext;\n  try {\n    return fn(a);\n  } finally {\n    // 回调执行完毕之后, 再恢复到以前的值 prevExecutionContext\n    executionContext = prevExecutionContext;\n    // ... 省略无关代码\n  }\n}\n\nexport function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {\n  const prevExecutionContext = executionContext;\n  executionContext &= ~BatchedContext;\n  executionContext |= LegacyUnbatchedContext;\n  try {\n    return fn(a);\n  } finally {\n    executionContext = prevExecutionContext;\n    // ... 省略无关代码\n  }\n}\n\n// ... 省略其他函数\n```\n\n这些函数的共性:\n\n1. 执行回调之前, 先保存当前值为`prevExecutionContext`, 再改变 `executionContext`.\n2. 在执行回调`fn`期间, 无论函数`fn`调用栈有多深, 被改变过的`executionContext`始终有效.\n3. 回调执行完毕之后, 恢复到以前的值 `prevExecutionContext`.\n\n## 总结\n\n本节主要介绍了`栈`在`react`源码中的使用情况. 涉及入栈出栈等基本操作(`Context` 状态管理), 以及对函数调用栈的巧妙运用(改变`executionContext`执行上下文).\n\n由于`reconciler`过程是一个深度优先遍历过程, 对于`fiber树`来讲, 向下探寻(`beginWork`阶段)和向上回溯(`completeWork`阶段)天然就和栈的入栈(`push`)和出栈(`pop`)能够无缝配合(context 机制就是在这个特性上建立起来的).\n\n## 参考资料\n\n- [栈](https://blog.csdn.net/K346K346/article/details/80849966)\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\ntitle: 图解React原理系列\ndescription: 图解React原理系列, 以react核心包结构和运行机制为主线索进行展开. 包括react 基本包结构, react 工作循环, react 启动模式, react fiber原理, react hook原理, react 合成事件等核心内容\nkeywords:\n  [\n    react,\n    react原理,\n    react fiber,\n    react hook,\n    react 合成事件,\n    react 工作循环,\n    react 启动模式,\n    react 基本包结构,\n  ]\nhero:\n  title: 图解React原理\n  description: 基于[`react@17.0.2`](https://github.com/facebook/react/tree/v17.0.2)(尽可能跟随 react 版本的升级, 持续更新). 用大量配图的方式, 致力于将`react原理`表述清楚.\n  actions:\n    - text: 开始学习\n      link: /main/macro-structure\nfooter: Open-source GPL-3 Licensed | Copyright © [公里柒(KM.Seven)](https://github.com/7kms)<br />Powered by [dumi](https://d.umijs.org/)\n---\n\n## 使用指南\n\n1. 本系列以 react 核心包结构和运行机制为主线索进行展开. 包括`react 宏观结构`, `react 工作循环`, `react 启动模式`, `react fiber原理`, `react hook原理`, `react 合成事件`等核心内容.\n2. 开源作品需要社区的净化和参与, 如有表述不清晰或表述错误, 欢迎[issue 勘误](https://github.com/7kms/react-illustration-series/issues). 如果对你有帮助, 请不吝 star.\n3. 本系列最初写作于 2020 年 6 月(当时稳定版本是 v16.13.1), 随着 react 官方的升级, 本 repo 会将主要版本的文章保存在以版本号命名的分支中.\n4. 当下前端技术圈总体比较浮躁, 各技术平台充斥着不少\"标题党\". 真正对于技术本身, 不能急于求成, 需要静下心来修炼.\n5. 本系列不是面经, 但会列举一些面试题来加深对 react 理解.\n6. 本系列所有内容皆为原创, 如需转载, 请注明出处.\n\n## 适用读者\n\n1. 对`react`,`react-dom`开发 web 应用有实践经验.\n2. 期望深入理解`react`内在作用原理.\n"
  },
  {
    "path": "docs/interview/01-setstate.md",
    "content": "---\ntitle: setState\ndescription: React中, setState是同步还是异步\nkeywords: [react, setState, react同步, react异步]\norder: 0\n---\n\n# React 中, setState 是同步还是异步\n\n所谓同步还是异步指的是调用 setState 之后是否马上能得到最新的 state\n\n不仅仅是`setState`了, 在对 function 类型组件中的 hook 进行操作时也是一样, 最终决定`setState`是同步渲染还是异步渲染的关键因素是`ReactFiberWorkLoop`工作空间的执行上下文.\n\n具体代码如下:\n\n```js\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber,\n  expirationTime: ExpirationTime,\n) {\n  const priorityLevel = getCurrentPriorityLevel();\n\n  if (expirationTime === Sync) {\n    if (\n      // Check if we're inside unbatchedUpdates\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      // Check if we're not already rendering\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      performSyncWorkOnRoot(root);\n    } else {\n      ensureRootIsScheduled(root);\n      schedulePendingInteractions(root, expirationTime);\n      if (executionContext === NoContext) {\n        // Flush the synchronous work now, unless we're already working or inside\n        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of\n        // scheduleCallbackForFiber to preserve the ability to schedule a callback\n        // without immediately flushing it. We only do this for user-initiated\n        // updates, to preserve historical behavior of legacy mode.\n        flushSyncCallbackQueue();\n      }\n    }\n  } else {\n    // Schedule a discrete update but only if it's not Sync.\n    if (\n      (executionContext & DiscreteEventContext) !== NoContext &&\n      // Only updates at user-blocking priority or greater are considered\n      // discrete, even inside a discrete event.\n      (priorityLevel === UserBlockingPriority ||\n        priorityLevel === ImmediatePriority)\n    ) {\n      // This is the result of a discrete event. Track the lowest priority\n      // discrete update per root so we can flush them early, if needed.\n      if (rootsWithPendingDiscreteUpdates === null) {\n        rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);\n      } else {\n        const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);\n        if (\n          lastDiscreteTime === undefined ||\n          lastDiscreteTime > expirationTime\n        ) {\n          rootsWithPendingDiscreteUpdates.set(root, expirationTime);\n        }\n      }\n    }\n    // Schedule other updates after in case the callback is sync.\n    ensureRootIsScheduled(root);\n    schedulePendingInteractions(root, expirationTime);\n  }\n}\n```\n\n可以看到, 是否同步渲染调度决定代码是`flushSyncCallbackQueue()`. 进入该分支的条件:\n\n1. 必须是`legacy模式`, `concurrent`模式下`expirationTime`不会为`Sync`\n2. `executionContext === NoContext`, 执行上下文必须要为空.\n\n两个条件缺一不可.\n\n## 结论\n\n同步:\n\n1. 首先在`legacy模式`下\n2. 在执行上下文为空的时候去调用`setState`\n   - 可以使用异步调用如`setTimeout`, `Promise`, `MessageChannel`等\n   - 可以监听原生事件, 注意不是合成事件, 在原生事件的回调函数中执行 setState 就是同步的\n\n异步:\n\n1. 如果是合成事件中的回调, `executionContext |= DiscreteEventContext`, 所以不会进入, 最终表现出异步\n2. concurrent 模式下都为异步\n\n## 演示示例\n\n```jsx\nimport React from 'react';\n\nexport default class App extends React.Component {\n  state = {\n    count: 0,\n  };\n\n  changeState = () => {\n    const newCount = this.state.count + 1;\n    this.setState({\n      count: this.state.count + 1,\n    });\n    if (newCount === this.state.count) {\n      console.log('同步执行render');\n    } else {\n      console.log('异步执行render');\n    }\n  };\n\n  changeState2 = () => {\n    const newCount = this.state.count + 1;\n    Promise.resolve().then(() => {\n      this.setState({\n        count: this.state.count + 1,\n      });\n      if (newCount === this.state.count) {\n        console.log('同步执行render');\n      } else {\n        console.log('异步执行render');\n      }\n    });\n  };\n\n  render() {\n    return (\n      <div>\n        <p>当前count={this.state.count}</p>\n        <button onClick={this.changeState}>异步+1</button>\n        <button onClick={this.changeState2}>同步+1</button>\n      </div>\n    );\n  }\n}\n```\n\n在看一个 concurrent 模式下的例子, 相同的代码都为异步 render:\n\n[![Edit boring-faraday-m7jtx](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/boring-faraday-m7jtx?fontsize=14&hidenavigation=1&theme=dark)\n"
  },
  {
    "path": "docs/interview/06-key.md",
    "content": "---\ntitle: React中key的作用\ndescription: React中, key 有什么作用, 可以省略吗?\nkeywords: [react, key]\norder: 1\n---\n\n# key 有什么作用, 可以省略吗?\n\n在 react 组件开发的过程中, `key`是一个常用的属性值, 多用于列表开发. 本文从源码的角度, 分析`key`在`react`内部是如何使用的, `key`是否可以省略.\n\n## ReactElement 对象\n\n我们在编程时直接书写的`jsx`代码, 实际上是会被编译成 ReactElement 对象, 所以`key`是`ReactElement对象`的一个属性.\n\n### 构造函数\n\n在把`jsx`转换成`ReactElement对象`的语法时, 有一个兼容问题. 会根据编译器的不同策略, 编译成 2 种方案.\n\n1. 最新的转译策略: 会将`jsx`语法的代码, 转译成`jsx()`函数包裹\n\n   `jsx`函数: 只保留与`key`相关的代码(其余源码本节不讨论)\n\n   ```js\n   /**\n    * https://github.com/reactjs/rfcs/pull/107\n    * @param {*} type\n    * @param {object} props\n    * @param {string} key\n    */\n   export function jsx(type, config, maybeKey) {\n     let propName;\n\n     // 1. key的默认值是null\n     let key = null;\n\n     // Currently, key can be spread in as a prop. This causes a potential\n     // issue if key is also explicitly declared (ie. <div {...props} key=\"Hi\" />\n     // or <div key=\"Hi\" {...props} /> ). We want to deprecate key spread,\n     // but as an intermediary step, we will use jsxDEV for everything except\n     // <div {...props} key=\"Hi\" />, because we aren't currently able to tell if\n     // key is explicitly declared to be undefined or not.\n     if (maybeKey !== undefined) {\n       key = '' + maybeKey;\n     }\n\n     if (hasValidKey(config)) {\n       // 2. 将key转换成字符串\n       key = '' + config.key;\n     }\n     // 3. 将key传入构造函数\n     return ReactElement(\n       type,\n       key,\n       ref,\n       undefined,\n       undefined,\n       ReactCurrentOwner.current,\n       props,\n     );\n   }\n   ```\n\n2. 传统的转译策略: 会将`jsx`语法的代码, 转译成[React.createElement()函数包裹](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactElement.js#L126-L146)\n\n   `React.createElement()函数`: 只保留与`key`相关的代码(其余源码本节不讨论)\n\n   ```js\n   /**\n    * Create and return a new ReactElement of the given type.\n    * See https://reactjs.org/docs/react-api.html#createelement\n    */\n   export function createElement(type, config, children) {\n     let propName;\n\n     // Reserved names are extracted\n     const props = {};\n\n     let key = null;\n     let ref = null;\n     let self = null;\n     let source = null;\n\n     if (config != null) {\n       if (hasValidKey(config)) {\n         key = '' + config.key; // key转换成字符串\n       }\n     }\n\n     return ReactElement(\n       type,\n       key,\n       ref,\n       self,\n       source,\n       ReactCurrentOwner.current,\n       props,\n     );\n   }\n   ```\n\n可以看到无论采取哪种编译方式, 核心逻辑都是一致的:\n\n1. `key`的默认值是`null`\n2. 如果外界有显式指定的`key`, 则将`key`转换成字符串类型.\n3. 调用`ReactElement`这个构造函数, 并且将`key`传入.\n\n```js\n// ReactElement的构造函数: 本节就先只关注其中的key属性\nconst ReactElement = function (type, key, ref, self, source, owner, props) {\n  const element = {\n    $$typeof: REACT_ELEMENT_TYPE,\n    type: type,\n    key: key,\n    ref: ref,\n    props: props,\n    _owner: owner,\n  };\n  return element;\n};\n```\n\n源码看到这里, 虽然还只是个皮毛, 但是起码知道了`key`的默认值是`null`. 所以任何一个`reactElement`对象, 内部都是有`key`值的, 只是一般情况下(对于单节点)很少显式去传入一个 key.\n\n## Fiber 对象\n\n`react`的核心运行逻辑, 是一个从输入到输出的过程(回顾[reconciler 运作流程](../main/reconciler-workflow.md)). 编程直接操作的`jsx`是`reactElement对象`,我们(程序员)的数据模型是`jsx`, 而`react内核`的数据模型是`fiber树形结构`. 所以要深入认识`key`还需要从`fiber`的视角继续来看.\n\n`fiber`对象是在`fiber树构造循环`过程中构造的, 其构造函数如下:\n\n```js\nfunction FiberNode(\n  tag: WorkTag,\n  pendingProps: mixed,\n  key: null | string,\n  mode: TypeOfMode,\n) {\n  this.tag = tag;\n  this.key = key; // 重点: key也是`fiber`对象的一个属性\n\n  // ...\n  this.elementType = null;\n  this.type = null;\n  this.stateNode = null;\n  // ... 省略无关代码\n}\n```\n\n可以看到, `key`也是`fiber`对象的一个属性. 这里和`reactElement`的情况有所不同:\n\n1. `reactElement`中的`key`是由`jsx`编译而来, `key`是由程序员直接控制的(即使是动态生成, 那也是直接控制)\n2. `fiber`对象是由`react`内核在运行时创建的, 所以`fiber.key`也是`react`内核进行设置的, 程序员没有直接控制.\n\n注意: `fiber.key`是`reactElement.key`的拷贝, 他们是完全相等的(包括`null`默认值).\n\n接下来分析`fiber`创建, 剖析`key`在这个过程中的具体使用情况.\n\n`fiber`对象的创建发生在`fiber树构造循环`阶段中, 具体来讲, 是在`reconcileChildren`调和函数中进行创建.\n\n## reconcileChildren 调和函数\n\n`reconcileChildren`是`react`中的一个`明星`函数, 最热点的问题就是`diff算法原理`, 事实上, `key`的作用完全就是为了`diff算法`服务的.\n\n> 注意: 本节只分析 key 相关的逻辑, 对于调和函数的算法原理, 请回顾算法章节[React 算法之调和算法](../algorithm/diff.md)\n\n调和函数源码(本节示例, 只摘取了部分代码):\n\n```js\nfunction ChildReconciler(shouldTrackSideEffects) {\n  function reconcileChildFibers(\n    returnFiber: Fiber,\n    currentFirstChild: Fiber | null,\n    newChild: any,\n    lanes: Lanes,\n  ): Fiber | null {\n    // Handle object types\n    const isObject = typeof newChild === 'object' && newChild !== null;\n\n    if (isObject) {\n      switch (newChild.$$typeof) {\n        case REACT_ELEMENT_TYPE:\n          // newChild是单节点\n          return placeSingleChild(\n            reconcileSingleElement(\n              returnFiber,\n              currentFirstChild,\n              newChild,\n              lanes,\n            ),\n          );\n      }\n    }\n    //  newChild是多节点\n    if (isArray(newChild)) {\n      return reconcileChildrenArray(\n        returnFiber,\n        currentFirstChild,\n        newChild,\n        lanes,\n      );\n    }\n    // ...\n  }\n\n  return reconcileChildFibers;\n}\n```\n\n### 单节点\n\n这里先看单节点的情况`reconcileSingleElement`(只保留与`key`有关的逻辑):\n\n```js\nfunction reconcileSingleElement(\n  returnFiber: Fiber,\n  currentFirstChild: Fiber | null,\n  element: ReactElement,\n  lanes: Lanes,\n): Fiber {\n  const key = element.key;\n  let child = currentFirstChild;\n  while (child !== null) {\n    //重点1: key是单节点是否复用的第一判断条件\n    if (child.key === key) {\n      switch (child.tag) {\n        default: {\n          if (child.elementType === element.type) {\n            // 第二判断条件\n            deleteRemainingChildren(returnFiber, child.sibling);\n            // 节点复用: 调用useFiber\n            const existing = useFiber(child, element.props);\n            existing.ref = coerceRef(returnFiber, child, element);\n            existing.return = returnFiber;\n            return existing;\n          }\n          break;\n        }\n      }\n      // Didn't match.\n      deleteRemainingChildren(returnFiber, child);\n      break;\n    }\n    child = child.sibling;\n  }\n  // 重点2: fiber节点创建, `key`是随着`element`对象被传入`fiber`的构造函数\n  const created = createFiberFromElement(element, returnFiber.mode, lanes);\n  created.ref = coerceRef(returnFiber, currentFirstChild, element);\n  created.return = returnFiber;\n  return created;\n}\n```\n\n可以看到, 对于单节点来讲, 有 2 个重点:\n\n1. `key`是单节点是否复用的第一判断条件(第二判断条件是`type`是否改变).\n   - 如果`key`不同, 其他条件是完全不看的\n2. 在新建节点时, `key`随着`element`对象被传入`fiber`的构造函数.\n\n所以到这里才是`key`的最核心作用, 是调和函数中, 针对单节点是否可以复用的`第一判断条件`.\n\n对于单节点来讲, `key`是可以省略的, `react`内部会设置成默认值`null`. 在进行`diff`时, 由于`null===null`为`true`, 前后`render`的`key`是一致的, 可以进行复用比较.\n\n如果单节点显式设置了`key`, 且两次`render`时的`key`如果不一致, 则无法复用.\n\n### 多节点\n\n继续查看多节点相关的逻辑:\n\n```js\nfunction reconcileChildrenArray(\n  returnFiber: Fiber,\n  currentFirstChild: Fiber | null,\n  newChildren: Array<*>,\n  lanes: Lanes,\n): Fiber | null {\n  if (__DEV__) {\n    // First, validate keys.\n    let knownKeys = null;\n    for (let i = 0; i < newChildren.length; i++) {\n      const child = newChildren[i];\n      // 1. 在dev环境下, 执行warnOnInvalidKey.\n      //  - 如果没有设置key, 会警告提示, 希望能显式设置key\n      //  - 如果key重复, 会错误提示.\n      knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);\n    }\n  }\n\n  let resultingFirstChild: Fiber | null = null;\n  let previousNewFiber: Fiber | null = null;\n\n  let oldFiber = currentFirstChild;\n  let lastPlacedIndex = 0;\n  let newIdx = 0;\n  let nextOldFiber = null;\n  // 第一次循环: 只会在更新阶段发生\n  for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {\n    if (oldFiber.index > newIdx) {\n      nextOldFiber = oldFiber;\n      oldFiber = null;\n    } else {\n      nextOldFiber = oldFiber.sibling;\n    }\n    // 1. 调用updateSlot, 处理公共序列中的fiber\n    const newFiber = updateSlot(\n      returnFiber,\n      oldFiber,\n      newChildren[newIdx],\n      lanes,\n    );\n    if (newFiber === null) {\n      // 如果无法复用, 则退出公共序列的遍历\n      if (oldFiber === null) {\n        oldFiber = nextOldFiber;\n      }\n      break;\n    }\n  }\n\n  // 第二次循环\n  if (oldFiber === null) {\n    for (; newIdx < newChildren.length; newIdx++) {\n      // 2. 调用createChild直接创建新fiber\n      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);\n    }\n    return resultingFirstChild;\n  }\n\n  for (; newIdx < newChildren.length; newIdx++) {\n    // 3. 调用updateFromMap处理非公共序列中的fiber\n    const newFiber = updateFromMap(\n      existingChildren,\n      returnFiber,\n      newIdx,\n      newChildren[newIdx],\n      lanes,\n    );\n  }\n\n  return resultingFirstChild;\n}\n```\n\n在`reconcileChildrenArray`中, 有 3 处调用与`fiber`有关(当然顺便就和`key`有关了), 它们分别是:\n\n1. `updateSlot`\n\n   ```js\n   function updateSlot(\n     returnFiber: Fiber,\n     oldFiber: Fiber | null,\n     newChild: any,\n     lanes: Lanes,\n   ): Fiber | null {\n     const key = oldFiber !== null ? oldFiber.key : null;\n\n     if (typeof newChild === 'object' && newChild !== null) {\n       switch (newChild.$$typeof) {\n         case REACT_ELEMENT_TYPE: {\n           //重点: key用于是否复用的第一判断条件\n           if (newChild.key === key) {\n             return updateElement(returnFiber, oldFiber, newChild, lanes);\n           } else {\n             return null;\n           }\n         }\n       }\n     }\n\n     return null;\n   }\n   ```\n\n2. `createChild`\n\n   ```js\n   function createChild(\n     returnFiber: Fiber,\n     newChild: any,\n     lanes: Lanes,\n   ): Fiber | null {\n     if (typeof newChild === 'object' && newChild !== null) {\n       switch (newChild.$$typeof) {\n         case REACT_ELEMENT_TYPE: {\n           // 重点: 调用构造函数进行创建\n           const created = createFiberFromElement(\n             newChild,\n             returnFiber.mode,\n             lanes,\n           );\n           return created;\n         }\n       }\n     }\n\n     return null;\n   }\n   ```\n\n3. `updateFromMap`\n\n   ```js\n   function updateFromMap(\n     existingChildren: Map<string | number, Fiber>,\n     returnFiber: Fiber,\n     newIdx: number,\n     newChild: any,\n     lanes: Lanes,\n   ): Fiber | null {\n     if (typeof newChild === 'object' && newChild !== null) {\n       switch (newChild.$$typeof) {\n         case REACT_ELEMENT_TYPE: {\n           //重点: key用于是否复用的第一判断条件\n           const matchedFiber =\n             existingChildren.get(\n               newChild.key === null ? newIdx : newChild.key,\n             ) || null;\n           return updateElement(returnFiber, matchedFiber, newChild, lanes);\n         }\n       }\n       return null;\n     }\n   }\n   ```\n\n针对多节点的`diff算法`可以分为 3 步骤(请回顾算法章节[React 算法之调和算法](../algorithm/diff.md)):\n\n1. 第一次循环: 比较公共序列\n   - 从左到右逐一遍历, 遇到一个无法复用的节点则退出循环.\n2. 第二次循环: 比较非公共序列\n   - 在第一次循环的基础上, 如果`oldFiber`队列遍历完了, 证明`newChildren`队列中剩余的对象全部都是新增.\n   - 此时继续遍历剩余的`newChildren`队列即可, 没有额外的`diff`比较.\n   - 在第一次循环的基础上, 如果`oldFiber`队列没有遍历完, 需要将`oldFiber`队列中剩余的对象都添加到一个`map`集合中, 以`oldFiber.key`作为键.\n   - 此时则在遍历剩余的`newChildren`队列时, 需要用`newChild.key`到`map`集合中进行查找, 如果匹配上了, 就将`oldFiber`从`map`中取出来, 同`newChild`进行`diff`比较.\n3. 清理工作\n   - 在第二次循环结束后, 如果`map`集合中还有剩余的`oldFiber`,则可以证明这些`oldFiber`都是被删除的节点, 需要打上删除标记.\n\n通过回顾`diff算法`的原理, 可以得到`key`在多节点情况下的特性:\n\n1. 新队列`newChildren`中的每一个对象(即`reactElement`对象)都需要同旧队列`oldFiber`中有相同`key`值的对象(即`oldFiber`对象)进行是否可复用的比较. `key`就是新旧对象能够对应起来的唯一标识.\n2. 如果省略`key`或者直接使用列表`index`作为`key`, 表现是一样的(`key=null`时, 会采用`index`代替`key`进行比较). 在新旧对象比较时, 只能按照`index`顺序进行比较, 复用的成功率大大降低, 大列表会出现性能问题.\n   - 例如一个排序的场景: `oldFiber`队列有 100 个, `newChildren`队列有 100 个(但是打乱了顺序). 由于没有设置`key`, 就会导致`newChildren`中的第 n 个必然要和`oldFiber`队列中的第 n 个进行比较, 这时它们的`key`完全一致(都是`null`), 由于顺序变了导致`props`不同, 所以新的`fiber`完全要走更新逻辑(理论上比新创建一个的性能还要耗).\n   - 同样是排序场景可以出现的 bug: 上面的场景只是性能差(又不是不能用), `key`使用不当还会造成`bug`\n   - 还是上述排序场景, 只是列表中的每一个`item`内部又是一个组件, 且其中某一个`item`使用了局部状态(比如`class组件`里面的`state`). 当第二次`render`时, `fiber`对象不会`delete`只会`update`导致新组件的`state`还沿用了上一次相同位置的旧组件的`state`, 造成了状态混乱.\n\n## 总结\n\n在`react`中`key`是服务于`diff算法`, 它的默认值是`null`, 在`diff算法`过程中, 新旧节点是否可以复用, 首先就会判定`key`是否相同, 其后才会进行其他条件的判定. 在源码中, 针对多节点(即列表组件)如果直接将`key`设置成`index`和不设置任何值的处理方案是一样的, 如果使用不当, 轻则造成性能损耗, 重则引起状态混乱造成 bug.\n"
  },
  {
    "path": "docs/interview/index.md",
    "content": "---\nnav:\n  title: 面试题\n  order: 2\ntitle: 使用说明\ngroup: interview\n---\n\n## 使用说明\n\n1. 题目会不断扩充, 每个题目对应有 issue, 可以在 issue 讨论\n2. 本系列不是面经, 列举一些面试题来加深对 react 理解.\n\n## 现有题目\n\n1. [setState 是同步还是异步?](./interview/01-setstate.md)\n2. class 组件生命周期有哪些?\n3. 重复调用 setState 会发生什么?\n4. 什么是 fiber 架构?\n5. 调和算法具体干什么的?\n6. [key 有什么作用,可以省略吗?](./interview/06-key.md)\n7. useState()如何实现数据持久化?\n8. useEffect()会有内存泄漏吗?\n9. `useEffect(function, deps)`中第二个参数表示依赖项, 如何实现依赖项的对比?\n10. 什么是可中断渲染?\n11. context 有什么作用?\n"
  },
  {
    "path": "docs/main/bootstrap.md",
    "content": "---\ntitle: 启动过程\ngroup: 运行核心\norder: 1\n---\n\n# React 应用的启动过程\n\n在前文[`reconciler 运作流程`](./reconciler-workflow.md)把`reconciler`的流程归结成 4 个步骤.\n\n本章节主要讲解`react`应用程序的启动过程, 位于`react-dom`包, 衔接`reconciler 运作流程`中的[`输入`](./reconciler-workflow.md#输入)步骤.\n\n在正式分析源码之前, 先了解一下`react`应用的`启动模式`:\n\n在当前稳定版`react@17.0.2`源码中, 有 3 种启动方式. 先引出官网上对于[这 3 种模式的介绍](https://zh-hans.reactjs.org/docs/concurrent-mode-adoption.html#why-so-many-modes), 其基本说明如下:\n\n1. `legacy` 模式: `ReactDOM.render(<App />, rootNode)`. 这是当前 React app 使用的方式. 这个模式可能不支持[这些新功能(concurrent 支持的所有功能)](https://zh-hans.reactjs.org/docs/concurrent-mode-patterns.html#the-three-steps).\n\n   ```js\n   // LegacyRoot\n   ReactDOM.render(<App />, document.getElementById('root'), (dom) => {}); // 支持callback回调, 参数是一个dom对象\n   ```\n\n2. [Blocking 模式](https://zh-hans.reactjs.org/docs/concurrent-mode-adoption.html#migration-step-blocking-mode): `ReactDOM.createBlockingRoot(rootNode).render(<App />)`. 目前正在实验中, 它仅提供了 `concurrent` 模式的小部分功能, 作为迁移到 `concurrent` 模式的第一个步骤.\n\n   ```js\n   // BlockingRoot\n   // 1. 创建ReactDOMRoot对象\n   const reactDOMBlockingRoot = ReactDOM.createBlockingRoot(\n     document.getElementById('root'),\n   );\n   // 2. 调用render\n   reactDOMBlockingRoot.render(<App />); // 不支持回调\n   ```\n\n3. [Concurrent 模式](https://zh-hans.reactjs.org/docs/concurrent-mode-adoption.html#enabling-concurrent-mode): `ReactDOM.createRoot(rootNode).render(<App />)`. 目前在实验中, 未来稳定之后，打算作为 React 的默认开发模式. 这个模式开启了所有的新功能.\n\n   ```js\n   // ConcurrentRoot\n   // 1. 创建ReactDOMRoot对象\n   const reactDOMRoot = ReactDOM.createRoot(document.getElementById('root'));\n   // 2. 调用render\n   reactDOMRoot.render(<App />); // 不支持回调\n   ```\n\n注意: 虽然`17.0.2`的源码中有[`createRoot`和`createBlockingRoot`方法](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOM.js#L202)(如果自行构建, [会默认构建`experimental`版本](https://github.com/facebook/react/blob/v17.0.2/scripts/rollup/build.js#L30-L35)), 但是稳定版的构建入口[排除掉了这两个 api](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/index.stable.js), 所以实际在`npm i react-dom`安装`17.0.2`稳定版后, 不能使用该 api.如果要想体验非`legacy`模式, 需要[显示安装 alpha 版本](https://github.com/reactwg/react-18/discussions/9)(或自行构建).\n\n## 启动流程\n\n在调用入口函数之前,`reactElement(<App/>)`和 DOM 对象`div#root`之间没有关联, 用图片表示如下:\n\n![](../../snapshots/bootstrap/process-before.png)\n\n### 创建全局对象 {#create-global-obj}\n\n无论`Legacy, Concurrent或Blocking`模式, react 在初始化时, 都会创建 3 个全局对象\n\n1. [`ReactDOM(Blocking)Root`对象](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMRoot.js#L62-L72)\n\n- 属于`react-dom`包, 该对象[暴露有`render,unmount`方法](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMRoot.js#L62-L104), 通过调用该实例的`render`方法, 可以引导 react 应用的启动.\n\n2. [`fiberRoot`对象](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberRoot.old.js#L83-L103)\n\n   - 属于`react-reconciler`包, 作为`react-reconciler`在运行过程中的全局上下文, 保存 fiber 构建过程中所依赖的全局状态.\n   - 其大部分实例变量用来存储`fiber 构造循环`(详见[`两大工作循环`](./workloop.md))过程的各种状态.react 应用内部, 可以根据这些实例变量的值, 控制执行逻辑.\n\n3. [`HostRootFiber`对象](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiber.old.js#L431-L449)\n   - 属于`react-reconciler`包, 这是 react 应用中的第一个 Fiber 对象, 是 Fiber 树的根节点, 节点的类型是`HostRoot`.\n\n这 3 个对象是 react 体系得以运行的基本保障, 一经创建大多数场景不会再销毁(除非卸载整个应用`root.unmount()`).\n\n这一过程是从`react-dom`包发起, 内部调用了`react-reconciler`包, 核心流程图如下(其中红色标注了 3 个对象的创建时机).\n\n![](../../snapshots/bootstrap/function-call.png)\n\n下面逐一解释这 3 个对象的创建过程.\n\n### 创建 ReactDOM(Blocking)Root 对象\n\n由于 3 种模式启动的 api 有所不同, 所以从源码上追踪, 也对应了 3 种方式. 最终都 new 一个`ReactDOMRoot`或`ReactDOMBlockingRoot`的实例, 需要创建过程中`RootTag`参数, 3 种模式各不相同. 该`RootTag`的类型决定了整个 react 应用是否支持[可中断渲染(后文有解释)](#可中断渲染).\n\n下面根据 3 种 mode 下的启动函数逐一分析.\n\n#### legacy 模式\n\n`legacy`模式表面上是直接调用`ReactDOM.render`, 跟踪`ReactDOM.render`后续调用`legacyRenderSubtreeIntoContainer`([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMLegacy.js#L175-L222))\n\n```js\nfunction legacyRenderSubtreeIntoContainer(\n  parentComponent: ?React$Component<any, any>,\n  children: ReactNodeList,\n  container: Container,\n  forceHydrate: boolean,\n  callback: ?Function,\n) {\n  let root: RootType = (container._reactRootContainer: any);\n  let fiberRoot;\n  if (!root) {\n    // 初次调用, root还未初始化, 会进入此分支\n    //1. 创建ReactDOMRoot对象, 初始化react应用环境\n    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(\n      container,\n      forceHydrate,\n    );\n    fiberRoot = root._internalRoot;\n    if (typeof callback === 'function') {\n      const originalCallback = callback;\n      callback = function () {\n        // instance最终指向 children(入参: 如<App/>)生成的dom节点\n        const instance = getPublicRootInstance(fiberRoot);\n        originalCallback.call(instance);\n      };\n    }\n    // 2. 更新容器\n    unbatchedUpdates(() => {\n      updateContainer(children, fiberRoot, parentComponent, callback);\n    });\n  } else {\n    // root已经初始化, 二次调用render会进入\n    // 1. 获取FiberRoot对象\n    fiberRoot = root._internalRoot;\n    if (typeof callback === 'function') {\n      const originalCallback = callback;\n      callback = function () {\n        const instance = getPublicRootInstance(fiberRoot);\n        originalCallback.call(instance);\n      };\n    }\n    // 2. 调用更新\n    updateContainer(children, fiberRoot, parentComponent, callback);\n  }\n  return getPublicRootInstance(fiberRoot);\n}\n```\n\n继续跟踪`legacyCreateRootFromDOMContainer`. 最后调用`new ReactDOMBlockingRoot(container, LegacyRoot, options);`\n\n```js\nfunction legacyCreateRootFromDOMContainer(\n  container: Container,\n  forceHydrate: boolean,\n): RootType {\n  const shouldHydrate =\n    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);\n  return createLegacyRoot(\n    container,\n    shouldHydrate\n      ? {\n          hydrate: true,\n        }\n      : undefined,\n  );\n}\n\nexport function createLegacyRoot(\n  container: Container,\n  options?: RootOptions,\n): RootType {\n  return new ReactDOMBlockingRoot(container, LegacyRoot, options); // 注意这里的LegacyRoot是固定的, 并不是外界传入的\n}\n```\n\n通过以上分析,`legacy`模式下调用`ReactDOM.render`有 2 个核心步骤:\n\n1. 创建`ReactDOMBlockingRoot`实例(在 Concurrent 模式和 Blocking 模式中详细分析该类), 初始化 react 应用环境.\n2. 调用`updateContainer`进行更新.\n\n#### Concurrent 模式和 Blocking 模式\n\n`Concurrent`模式和`Blocking`模式从调用方式上直接可以看出\n\n1. 分别调用`ReactDOM.createRoot`和`ReactDOM.createBlockingRoot`创建`ReactDOMRoot`和`ReactDOMBlockingRoot`实例\n2. 调用`ReactDOMRoot`和`ReactDOMBlockingRoot`实例的`render`方法\n\n```js\nexport function createRoot(\n  container: Container,\n  options?: RootOptions,\n): RootType {\n  return new ReactDOMRoot(container, options);\n}\n\nexport function createBlockingRoot(\n  container: Container,\n  options?: RootOptions,\n): RootType {\n  return new ReactDOMBlockingRoot(container, BlockingRoot, options); // 注意第2个参数BlockingRoot是固定写死的\n}\n```\n\n继续查看`ReactDOMRoot`和`ReactDOMBlockingRoot`对象\n\n```js\nfunction ReactDOMRoot(container: Container, options: void | RootOptions) {\n  // 创建一个fiberRoot对象, 并将其挂载到this._internalRoot之上\n  this._internalRoot = createRootImpl(container, ConcurrentRoot, options);\n}\nfunction ReactDOMBlockingRoot(\n  container: Container,\n  tag: RootTag,\n  options: void | RootOptions,\n) {\n  // 创建一个fiberRoot对象, 并将其挂载到this._internalRoot之上\n  this._internalRoot = createRootImpl(container, tag, options);\n}\n\nReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render =\n  function (children: ReactNodeList): void {\n    const root = this._internalRoot;\n    // 执行更新\n    updateContainer(children, root, null, null);\n  };\n\nReactDOMRoot.prototype.unmount = ReactDOMBlockingRoot.prototype.unmount =\n  function (): void {\n    const root = this._internalRoot;\n    const container = root.containerInfo;\n    // 执行更新\n    updateContainer(null, root, null, () => {\n      unmarkContainerAsRoot(container);\n    });\n  };\n```\n\n`ReactDOMRoot`和`ReactDOMBlockingRoot`有相同的特性\n\n1. 调用`createRootImpl`创建`fiberRoot`对象, 并将其挂载到`this._internalRoot`上.\n2. 原型上有`render`和`unmount`方法, 且内部都会调用`updateContainer`进行更新.\n\n### 创建 fiberRoot 对象 {#create-root-impl}\n\n无论哪种模式下, 在`ReactDOM(Blocking)Root`的创建过程中, 都会调用一个相同的函数`createRootImpl`, 查看后续的函数调用, 最后会创建`fiberRoot 对象`(在这个过程中, 特别注意`RootTag`的传递过程):\n\n```js\n// 注意: 3种模式下的tag是各不相同(分别是ConcurrentRoot,BlockingRoot,LegacyRoot).\nthis._internalRoot = createRootImpl(container, tag, options);\n```\n\n```js\nfunction createRootImpl(\n  container: Container,\n  tag: RootTag,\n  options: void | RootOptions,\n) {\n  // ... 省略部分源码(有关hydrate服务端渲染等, 暂时用不上)\n  // 1. 创建fiberRoot\n  const root = createContainer(container, tag, hydrate, hydrationCallbacks); // 注意RootTag的传递\n  // 2. 标记dom对象, 把dom和fiber对象关联起来\n  markContainerAsRoot(root.current, container);\n  // ...省略部分无关代码\n  return root;\n}\n```\n\n```js\nexport function createContainer(\n  containerInfo: Container,\n  tag: RootTag,\n  hydrate: boolean,\n  hydrationCallbacks: null | SuspenseHydrationCallbacks,\n): OpaqueRoot {\n  // 创建fiberRoot对象\n  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); // 注意RootTag的传递\n}\n```\n\n### 创建 HostRootFiber 对象\n\n在`createFiberRoot`中, 创建了`react`应用的首个`fiber`对象, 称为`HostRootFiber(fiber.tag = HostRoot)`\n\n```js\nexport function createFiberRoot(\n  containerInfo: any,\n  tag: RootTag,\n  hydrate: boolean,\n  hydrationCallbacks: null | SuspenseHydrationCallbacks,\n): FiberRoot {\n  // 创建fiberRoot对象, 注意RootTag的传递\n  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);\n\n  // 1. 这里创建了`react`应用的首个`fiber`对象, 称为`HostRootFiber`\n  const uninitializedFiber = createHostRootFiber(tag);\n  root.current = uninitializedFiber;\n  uninitializedFiber.stateNode = root;\n  // 2. 初始化HostRootFiber的updateQueue\n  initializeUpdateQueue(uninitializedFiber);\n\n  return root;\n}\n```\n\n在创建`HostRootFiber`时, 其中`fiber.mode`属性, 会与 3 种`RootTag`(`ConcurrentRoot`,`BlockingRoot`,`LegacyRoot`)关联起来.\n\n```js\nexport function createHostRootFiber(tag: RootTag): Fiber {\n  let mode;\n  if (tag === ConcurrentRoot) {\n    mode = ConcurrentMode | BlockingMode | StrictMode;\n  } else if (tag === BlockingRoot) {\n    mode = BlockingMode | StrictMode;\n  } else {\n    mode = NoMode;\n  }\n  return createFiber(HostRoot, null, null, mode); // 注意这里设置的mode属性是由RootTag决定的\n}\n```\n\n注意:`fiber`树中所有节点的`mode`都会和`HostRootFiber.mode`一致(新建的 fiber 节点, 其 mode 来源于父节点),所以**HostRootFiber.mode**非常重要, 它决定了以后整个 fiber 树构建过程.\n\n运行到这里, 3 个对象创建成功, `react`应用的初始化完毕.\n\n将此刻内存中各个对象的引用情况表示出来:\n\n1. legacy\n\n![](../../snapshots/bootstrap/process-legacy.png)\n\n2. concurrent\n\n![](../../snapshots/bootstrap/process-concurrent.png)\n\n3. blocking\n\n![](../../snapshots/bootstrap/process-blocking.png)\n\n注意:\n\n1. 3 种模式下,`HostRootFiber.mode`是不一致的\n2. legacy 下, `div#root`和`ReactDOMBlockingRoot`之间通过`_reactRootContainer`关联. 其他模式是没有关联的\n3. 此时`reactElement(<App/>)`还是独立在外的, 还没有和目前创建的 3 个全局对象关联起来\n\n## 调用更新入口\n\n1. legacy\n   回到`legacyRenderSubtreeIntoContainer`函数中有:\n\n```js\n// 2. 更新容器\nunbatchedUpdates(() => {\n  updateContainer(children, fiberRoot, parentComponent, callback);\n});\n```\n\n2. concurrent 和 blocking\n   在`ReactDOM(Blocking)Root`原型上有`render`方法\n\n```js\nReactDOMRoot.prototype.render = ReactDOMBlockingRoot.prototype.render =\n  function (children: ReactNodeList): void {\n    const root = this._internalRoot;\n    // 执行更新\n    updateContainer(children, root, null, null);\n  };\n```\n\n相同点:\n\n1. 3 种模式在调用更新时都会执行`updateContainer`. `updateContainer`函数串联了`react-dom`与`react-reconciler`, 之后的逻辑进入了`react-reconciler`包.\n\n不同点:\n\n1. `legacy`下的更新会先调用`unbatchedUpdates`, 更改执行上下文为`LegacyUnbatchedContext`, 之后调用`updateContainer`进行更新.\n\n2. `concurrent`和`blocking`不会更改执行上下文, 直接调用`updateContainer`进行更新.\n\n继续跟踪[`updateContainer`函数](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberReconciler.old.js#L250-L321)\n\n```js\nexport function updateContainer(\n  element: ReactNodeList,\n  container: OpaqueRoot,\n  parentComponent: ?React$Component<any, any>,\n  callback: ?Function,\n): Lane {\n  const current = container.current;\n  // 1. 获取当前时间戳, 计算本次更新的优先级\n  const eventTime = requestEventTime();\n  const lane = requestUpdateLane(current);\n\n  // 2. 设置fiber.updateQueue\n  const update = createUpdate(eventTime, lane);\n  update.payload = { element };\n  callback = callback === undefined ? null : callback;\n  if (callback !== null) {\n    update.callback = callback;\n  }\n  enqueueUpdate(current, update);\n\n  // 3. 进入reconciler运作流程中的`输入`环节\n  scheduleUpdateOnFiber(current, lane, eventTime);\n  return lane;\n}\n```\n\n`updateContainer`函数位于`react-reconciler`包中, 它串联了`react-dom`与`react-reconciler`. 此处暂时不深入分析`updateContainer`函数的具体功能, 需要关注其最后调用了`scheduleUpdateOnFiber`.\n\n在前文[`reconciler 运作流程`](./reconciler-workflow.md)中, 重点分析过`scheduleUpdateOnFiber`是`输入`阶段的入口函数.\n\n所以到此为止, 通过调用`react-dom`包的`api`(如: `ReactDOM.render`), `react`内部经过一系列运转, 完成了初始化, 并且进入了`reconciler 运作流程`的第一个阶段.\n\n## 思考\n\n### 可中断渲染\n\nreact 中最广为人知的可中断渲染(render 可以中断, 部分生命周期函数有可能执行多次, `UNSAFE_componentWillMount`,`UNSAFE_componentWillReceiveProps`)只有在`HostRootFiber.mode === ConcurrentRoot | BlockingRoot`才会开启. 如果使用的是`legacy`, 即通过`ReactDOM.render(<App/>, dom)`这种方式启动时`HostRootFiber.mode = NoMode`, 这种情况下无论是首次 render 还是后续 update 都只会进入同步工作循环, `reconciliation`没有机会中断, 所以生命周期函数只会调用一次.\n\n对于`可中断渲染`的宣传最早来自[2017 年 Lin Clark 的演讲](http://conf2017.reactjs.org/speakers/lin). 演讲中阐述了未来 react 会应用 fiber 架构, `reconciliation可中断`等(13:15 秒). 在[`v16.1.0`](https://github.com/facebook/react/blob/master/CHANGELOG.md#1610-november-9-2017)中应用了 fiber.\n\n在最新稳定版[`v17.0.2`](https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021)中, `可中断渲染`虽然实现, 但是并没有在稳定版暴露出 api. 只能[安装 alpha 版本](https://github.com/reactwg/react-18/discussions/9)才能体验该特性.\n\n但是不少开发人员认为稳定版本的`react`已经是可中断渲染(其实是有误区的), 大概率也是受到了各类宣传文章的影响. 前端大环境还是比较浮躁的, 在当下, 更需要静下心来学习.\n\n## 总结\n\n本章节介绍了`react`应用的 3 种启动方式. 分析了启动后创建了 3 个关键对象, 并绘制了对象在内存中的引用关系. 启动过程最后调用`updateContainer`进入`react-reconciler`包,进而调用`schedulerUpdateOnFiber`函数, 与`reconciler运作流程`中的`输入`阶段相衔接.\n"
  },
  {
    "path": "docs/main/context.md",
    "content": "---\ntitle: context 原理\ngroup: 状态管理\norder: 4\n---\n\n# React Context 原理\n\n简单来讲, `Context`提供了一种直接访问祖先节点上的状态的方法, 避免了多级组件层层传递`props`.\n\n有关`Context`的用法, 请直接查看官方文档, 本文将从`fiber树构造`的视角, 分析`Context`的实现原理.\n\n## 创建 Context\n\n根据官网示例, 通过`React.createContext`这个 api 来创建`context`对象. 在[createContext](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactContext.js#L14-L152)中, 可以看到`context`对象的数据结构:\n\n```js\nexport function createContext<T>(\n  defaultValue: T,\n  calculateChangedBits: ?(a: T, b: T) => number,\n): ReactContext<T> {\n  if (calculateChangedBits === undefined) {\n    calculateChangedBits = null;\n  }\n  const context: ReactContext<T> = {\n    $$typeof: REACT_CONTEXT_TYPE,\n    _calculateChangedBits: calculateChangedBits,\n    // As a workaround to support multiple concurrent renderers, we categorize\n    // some renderers as primary and others as secondary. We only expect\n    // there to be two concurrent renderers at most: React Native (primary) and\n    // Fabric (secondary); React DOM (primary) and React ART (secondary).\n    // Secondary renderers store their context values on separate fields.\n    _currentValue: defaultValue,\n    _currentValue2: defaultValue,\n    _threadCount: 0,\n    Provider: (null: any),\n    Consumer: (null: any),\n  };\n\n  context.Provider = {\n    $$typeof: REACT_PROVIDER_TYPE,\n    _context: context,\n  };\n  context.Consumer = context;\n  return context;\n}\n```\n\n`createContext`核心逻辑:\n\n- 其初始值保存在`context._currentValue`(同时保存到`context._currentValue2`. 英文注释已经解释, 保存 2 个 value 是为了支持多个渲染器并发渲染)\n- 同时创建了`context.Provider`, `context.Consumer`2 个`reactElement`对象.\n\n比如, 创建`const MyContext = React.createContext(defaultValue);`, 之后使用`<MyContext.Provider value={/* 某个值 */}>`声明一个`ContextProvider`类型的组件.\n\n在`fiber树渲染`时, 在`beginWork`中`ContextProvider`类型的节点对应的处理函数是[updateContextProvider](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L2842-L2898):\n\n```js\nfunction beginWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const updateLanes = workInProgress.lanes;\n  workInProgress.lanes = NoLanes;\n  // ...省略无关代码\n  switch (workInProgress.tag) {\n    case ContextProvider:\n      return updateContextProvider(current, workInProgress, renderLanes);\n    case ContextConsumer:\n      return updateContextConsumer(current, workInProgress, renderLanes);\n  }\n}\n\nfunction updateContextProvider(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n) {\n  // ...省略无关代码\n  const providerType: ReactProviderType<any> = workInProgress.type;\n  const context: ReactContext<any> = providerType._context;\n\n  const newProps = workInProgress.pendingProps;\n  const oldProps = workInProgress.memoizedProps;\n  // 接收新value\n  const newValue = newProps.value;\n\n  // 更新 ContextProvider._currentValue\n  pushProvider(workInProgress, newValue);\n\n  if (oldProps !== null) {\n    // ... 省略更新context的逻辑, 下文讨论\n  }\n\n  const newChildren = newProps.children;\n  reconcileChildren(current, workInProgress, newChildren, renderLanes);\n  return workInProgress.child;\n}\n```\n\n`updateContextProvider()`在`fiber初次创建`时十分简单, 仅仅就是保存了`pendingProps.value`做为`context`的最新值, 之后这个最新的值用于供给消费.\n\n### context.\\_currentValue 存储\n\n注意`updateContextProvider -> pushProvider`中的[pushProvider(workInProgress, newValue)](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L75-L113):\n\n```js\n// ...省略无关代码\nexport function pushProvider<T>(providerFiber: Fiber, nextValue: T): void {\n  const context: ReactContext<T> = providerFiber.type._context;\n  push(valueCursor, context._currentValue, providerFiber);\n  context._currentValue = nextValue;\n}\n```\n\n`pushProvider`实际上是一个存储函数, 利用`栈`的特性, 先把`context._currentValue`压栈, 之后更新`context._currentValue = nextValue`.\n\n与`pushProvider`对应的还有[popProvider](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L115-L126), 同样利用`栈`的特性, 把`栈`中的值弹出, 还原到`context._currentValue`中.\n\n本节重点分析`Context Api`在`fiber树构造`过程中的作用. 有关`pushProvider/popProvider`的具体实现过程(栈存储), 在[React 算法之栈操作](../algorithm/stack.md#context)中有详细图解.\n\n## 消费 Context\n\n使用了`MyContext.Provider`组件之后, 在`fiber树构造`过程中, context 的值会被`ContextProvider`类型的`fiber`节点所更新. 在后续的过程中, 如何读取`context._currentValue`?\n\n在`react`中, 共提供了 3 种方式可以消费`Context`:\n\n1. 使用`MyContext.Consumer`组件: 用于`JSX`. 如, `<MyContext.Consumer>(value)=>{}</MyContext.Consumer>`\n\n   - `beginWork`中, 对于`ContextConsumer`类型的节点, 对应的处理函数是[updateContextConsumer](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L2902-L2963)\n\n   ```js\n   function updateContextConsumer(\n     current: Fiber | null,\n     workInProgress: Fiber,\n     renderLanes: Lanes,\n   ) {\n     let context: ReactContext<any> = workInProgress.type;\n     const newProps = workInProgress.pendingProps;\n     const render = newProps.children;\n\n     // 读取context\n     prepareToReadContext(workInProgress, renderLanes);\n     const newValue = readContext(context, newProps.unstable_observedBits);\n     let newChildren;\n\n     // ...省略无关代码\n   }\n   ```\n\n2. 使用`useContext`: 用于`function`中. 如, `const value = useContext(MyContext)`\n\n   - 进入`updateFunctionComponent`后, 会调用`prepareToReadContext`\n   - 无论是初次[创建阶段](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1780), 还是[更新阶段](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1801), `useContext`都直接调用了`readContext`\n\n3. `class`组件中, 使用一个静态属性`contextType`: 用于`class`组件中获取`context`. 如, `MyClass.contextType = MyContext;`\n   - 进入`updateClassComponent`后, 会调用`prepareToReadContext`\n   - 无论[constructClassInstance](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L573),[mountClassInstance](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L807), [updateClassInstance](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L1031)内部都调用`context = readContext((contextType: any));`\n\n所以这 3 种方式只是`react`根据不同使用场景封装的`api`, 内部都会调用[prepareToReadContext](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L297-L317)和[readContext(contextType)](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L319-L381).\n\n```js\n// ... 省略无关代码\nexport function prepareToReadContext(\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): void {\n  // 1. 设置全局变量, 为readContext做准备\n  currentlyRenderingFiber = workInProgress;\n  lastContextDependency = null;\n  lastContextWithAllBitsObserved = null;\n\n  const dependencies = workInProgress.dependencies;\n  if (dependencies !== null) {\n    const firstContext = dependencies.firstContext;\n    if (firstContext !== null) {\n      if (includesSomeLane(dependencies.lanes, renderLanes)) {\n        // Context list has a pending update. Mark that this fiber performed work.\n        markWorkInProgressReceivedUpdate();\n      }\n      // Reset the work-in-progress list\n      dependencies.firstContext = null;\n    }\n  }\n}\n// ... 省略无关代码\nexport function readContext<T>(\n  context: ReactContext<T>,\n  observedBits: void | number | boolean,\n): T {\n  const contextItem = {\n    context: ((context: any): ReactContext<mixed>),\n    observedBits: resolvedObservedBits,\n    next: null,\n  };\n  // 1. 构造一个contextItem, 加入到 workInProgress.dependencies链表之后\n  if (lastContextDependency === null) {\n    lastContextDependency = contextItem;\n    currentlyRenderingFiber.dependencies = {\n      lanes: NoLanes,\n      firstContext: contextItem,\n      responders: null,\n    };\n  } else {\n    lastContextDependency = lastContextDependency.next = contextItem;\n  }\n  // 2. 返回 currentValue\n  return isPrimaryRenderer ? context._currentValue : context._currentValue2;\n}\n```\n\n核心逻辑:\n\n1. `prepareToReadContext`: 设置`currentlyRenderingFiber = workInProgress`, 并重置`lastContextDependency`等全局变量.\n2. `readContext`: 返回`context._currentValue`, 并构造一个`contextItem`添加到`workInProgress.dependencies`链表之后.\n\n注意: 这个`readContext`并不是纯函数, 它还有一些副作用, 会更改`workInProgress.dependencies`, 其中`contextItem.context`保存了当前`context`的引用. 这个`dependencies`属性会在更新时使用, 用于判定是否依赖了`ContextProvider`中的值.\n\n返回`context._currentValue`之后, 之后继续进行`fiber树构造`直到全部完成即可.\n\n## 更新 Context\n\n来到更新阶段, 同样进入`updateContextConsumer`\n\n```js\nfunction updateContextProvider(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n) {\n  const providerType: ReactProviderType<any> = workInProgress.type;\n  const context: ReactContext<any> = providerType._context;\n\n  const newProps = workInProgress.pendingProps;\n  const oldProps = workInProgress.memoizedProps;\n\n  const newValue = newProps.value;\n\n  pushProvider(workInProgress, newValue);\n\n  if (oldProps !== null) {\n    // 更新阶段进入\n    const oldValue = oldProps.value;\n    // 对比 newValue 和 oldValue\n    const changedBits = calculateChangedBits(context, newValue, oldValue);\n    if (changedBits === 0) {\n      // value没有变动, 进入 Bailout 逻辑\n      if (\n        oldProps.children === newProps.children &&\n        !hasLegacyContextChanged()\n      ) {\n        return bailoutOnAlreadyFinishedWork(\n          current,\n          workInProgress,\n          renderLanes,\n        );\n      }\n    } else {\n      // value变动, 查找对应的consumers, 并使其能够被更新\n      propagateContextChange(workInProgress, context, changedBits, renderLanes);\n    }\n  }\n  // ... 省略无关代码\n}\n```\n\n核心逻辑:\n\n1. `value`没有改变, 直接进入`Bailout`(可以回顾[fiber 树构造(对比更新)](./fibertree-update.md#bailout)中对`bailout`的解释).\n2. `value`改变, 调用`propagateContextChange`\n\n[propagateContextChange](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L182-L295):\n\n```js\nexport function propagateContextChange(\n  workInProgress: Fiber,\n  context: ReactContext<mixed>,\n  changedBits: number,\n  renderLanes: Lanes,\n): void {\n  let fiber = workInProgress.child;\n  if (fiber !== null) {\n    // Set the return pointer of the child to the work-in-progress fiber.\n    fiber.return = workInProgress;\n  }\n  while (fiber !== null) {\n    let nextFiber;\n    const list = fiber.dependencies;\n    if (list !== null) {\n      nextFiber = fiber.child;\n      let dependency = list.firstContext;\n      while (dependency !== null) {\n        // 检查 dependency中依赖的context\n        if (\n          dependency.context === context &&\n          (dependency.observedBits & changedBits) !== 0\n        ) {\n          // 符合条件, 安排调度\n          if (fiber.tag === ClassComponent) {\n            // class 组件需要创建一个update对象, 添加到updateQueue队列\n            const update = createUpdate(\n              NoTimestamp,\n              pickArbitraryLane(renderLanes),\n            );\n            update.tag = ForceUpdate; // 注意ForceUpdate, 保证class组件一定执行render\n            enqueueUpdate(fiber, update);\n          }\n          fiber.lanes = mergeLanes(fiber.lanes, renderLanes);\n          const alternate = fiber.alternate;\n          if (alternate !== null) {\n            alternate.lanes = mergeLanes(alternate.lanes, renderLanes);\n          }\n          // 向上\n          scheduleWorkOnParentPath(fiber.return, renderLanes);\n\n          // 标记优先级\n          list.lanes = mergeLanes(list.lanes, renderLanes);\n\n          // 退出查找\n          break;\n        }\n        dependency = dependency.next;\n      }\n    }\n\n    // ...省略无关代码\n    // ...省略无关代码\n\n    fiber = nextFiber;\n  }\n}\n```\n\n`propagateContextChange`源码比较长, 核心逻辑如下:\n\n1. 向下遍历: 从`ContextProvider`类型的节点开始, 向下查找所有`fiber.dependencies`依赖该`context`的节点(假设叫做`consumer`).\n2. 向上遍历: 从`consumer`节点开始, 向上遍历, 修改父路径上所有节点的`fiber.childLanes`属性, 表明其子节点有改动, 子节点会进入更新逻辑.\n\n   - 这一步通过调用[scheduleWorkOnParentPath(fiber.return, renderLanes)](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberNewContext.old.js#L155-L180)实现.\n\n     ```js\n     export function scheduleWorkOnParentPath(\n       parent: Fiber | null,\n       renderLanes: Lanes,\n     ) {\n       // Update the child lanes of all the ancestors, including the alternates.\n       let node = parent;\n       while (node !== null) {\n         const alternate = node.alternate;\n         if (!isSubsetOfLanes(node.childLanes, renderLanes)) {\n           node.childLanes = mergeLanes(node.childLanes, renderLanes);\n           if (alternate !== null) {\n             alternate.childLanes = mergeLanes(\n               alternate.childLanes,\n               renderLanes,\n             );\n           }\n         } else if (\n           alternate !== null &&\n           !isSubsetOfLanes(alternate.childLanes, renderLanes)\n         ) {\n           alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);\n         } else {\n           // Neither alternate was updated, which means the rest of the\n           // ancestor path already has sufficient priority.\n           break;\n         }\n         node = node.return;\n       }\n     }\n     ```\n\n   - `scheduleWorkOnParentPath`与[markUpdateLaneFromFiberToRoot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L625-L667)的作用相似, 具体可以回顾[fiber 树构造(对比更新)](./fibertree-update.md#markUpdateLaneFromFiberToRoot)\n\n通过以上 2 个步骤, 保证了所有消费该`context`的子节点都会被重新构造, 进而保证了状态的一致性, 实现了`context`更新.\n\n## 总结\n\n`Context`的实现思路还是比较清晰, 总体分为 2 步.\n\n1. 在消费状态时,`ContextConsumer`节点调用`readContext(MyContext)`获取最新状态.\n2. 在更新状态时, 由`ContextProvider`节点负责查找所有`ContextConsumer`节点, 并设置消费节点的父路径上所有节点的`fiber.childLanes`, 保证消费节点可以得到更新.\n"
  },
  {
    "path": "docs/main/fibertree-commit.md",
    "content": "---\ntitle: fiber 树渲染\ngroup: 运行核心\norder: 7\n---\n\n# fiber 树渲染\n\n在正式分析`fiber树渲染`之前, 再次回顾一下[reconciler 运作流程](./reconciler-workflow.md)的 4 个阶段:\n\n![](../../snapshots/reconciler-workflow/reactfiberworkloop.png)\n\n1. 输入阶段: 衔接`react-dom`包, 承接`fiber更新`请求(参考[React 应用的启动过程](./bootstrap.md)).\n2. 注册调度任务: 与调度中心(`scheduler`包)交互, 注册调度任务`task`, 等待任务回调(参考[React 调度原理(scheduler)](./scheduler.md)).\n3. 执行任务回调: 在内存中构造出`fiber树`和`DOM`对象(参考[fiber 树构造(初次创建)](./fibertree-create.md)和 fiber 树构造(对比更新)).\n4. 输出: 与渲染器(`react-dom`)交互, 渲染`DOM`节点.\n\n本节分析其中的第 4 阶段(输出), `fiber树渲染`处于`reconciler 运作流程`这一流水线的最后一环, 或者说前面的步骤都是为了最后一步服务, 所以其重要性不言而喻.\n\n前文已经介绍了`fiber树构造`, 现在分析`fiber树渲染`过程, 这个过程, 实际上是对`fiber树`的进一步处理.\n\n## fiber 树特点\n\n通过前文`fiber树构造`的解读, 可以总结出`fiber树`的基本特点:\n\n- 无论是`首次构造`或者是`对比更新`, 最终都会在内存中生成一棵用于渲染页面的`fiber树`(即`fiberRoot.finishedWork`).\n- 这棵将要被渲染的`fiber树`有 2 个特点:\n  1. 副作用队列挂载在根节点上(具体来讲是`finishedWork.firstEffect`)\n  2. 代表最新页面的`DOM`对象挂载在`fiber树`中首个`HostComponent`类型的节点上(具体来讲`DOM`对象是挂载在`fiber.stateNode`属性上)\n\n这里再次回顾前文使用过的 2 棵 fiber 树, 可以验证上述特点:\n\n1. 初次构造\n\n![](../../snapshots/fibertree-create/fibertree-beforecommit.png)\n\n2. 对比更新\n\n![](../../snapshots/fibertree-update/fibertree-beforecommit.png)\n\n## commitRoot\n\n整个渲染逻辑都在[commitRoot 函数中](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1879-L2254):\n\n```js\nfunction commitRoot(root) {\n  const renderPriorityLevel = getCurrentPriorityLevel();\n  runWithPriority(\n    ImmediateSchedulerPriority,\n    commitRootImpl.bind(null, root, renderPriorityLevel),\n  );\n  return null;\n}\n```\n\n在`commitRoot`中同时使用到了`渲染优先级`和`调度优先级`, 有关优先级的讨论, 在前文已经做出了说明(参考[React 中的优先级管理](./priority.md)和[fiber 树构造(基础准备)#优先级](./fibertree-prepare.md#优先级)), 本节不再赘述. 最后的实现是通过`commitRootImpl`函数:\n\n```js\n// ... 省略部分无关代码\nfunction commitRootImpl(root, renderPriorityLevel) {\n  // ============ 渲染前: 准备 ============\n\n  const finishedWork = root.finishedWork;\n  const lanes = root.finishedLanes;\n\n  // 清空FiberRoot对象上的属性\n  root.finishedWork = null;\n  root.finishedLanes = NoLanes;\n  root.callbackNode = null;\n\n  if (root === workInProgressRoot) {\n    // 重置全局变量\n    workInProgressRoot = null;\n    workInProgress = null;\n    workInProgressRootRenderLanes = NoLanes;\n  }\n\n  // 再次更新副作用队列\n  let firstEffect;\n  if (finishedWork.flags > PerformedWork) {\n    // 默认情况下fiber节点的副作用队列是不包括自身的\n    // 如果根节点有副作用, 则将根节点添加到副作用队列的末尾\n    if (finishedWork.lastEffect !== null) {\n      finishedWork.lastEffect.nextEffect = finishedWork;\n      firstEffect = finishedWork.firstEffect;\n    } else {\n      firstEffect = finishedWork;\n    }\n  } else {\n    firstEffect = finishedWork.firstEffect;\n  }\n\n  // ============ 渲染 ============\n  let firstEffect = finishedWork.firstEffect;\n  if (firstEffect !== null) {\n    const prevExecutionContext = executionContext;\n    executionContext |= CommitContext;\n    // 阶段1: dom突变之前\n    nextEffect = firstEffect;\n    do {\n      commitBeforeMutationEffects();\n    } while (nextEffect !== null);\n\n    // 阶段2: dom突变, 界面发生改变\n    nextEffect = firstEffect;\n    do {\n      commitMutationEffects(root, renderPriorityLevel);\n    } while (nextEffect !== null);\n    // 恢复界面状态\n    resetAfterCommit(root.containerInfo);\n    // 切换current指针\n    root.current = finishedWork;\n\n    // 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等\n    nextEffect = firstEffect;\n    do {\n      commitLayoutEffects(root, lanes);\n    } while (nextEffect !== null);\n    nextEffect = null;\n    executionContext = prevExecutionContext;\n  }\n\n  // ============ 渲染后: 重置与清理 ============\n  if (rootDoesHavePassiveEffects) {\n    // 有被动作用(使用useEffect), 保存一些全局变量\n  } else {\n    // 分解副作用队列链表, 辅助垃圾回收\n    // 如果有被动作用(使用useEffect), 会把分解操作放在flushPassiveEffects函数中\n    nextEffect = firstEffect;\n    while (nextEffect !== null) {\n      const nextNextEffect = nextEffect.nextEffect;\n      nextEffect.nextEffect = null;\n      if (nextEffect.flags & Deletion) {\n        detachFiberAfterEffects(nextEffect);\n      }\n      nextEffect = nextNextEffect;\n    }\n  }\n  // 重置一些全局变量(省略这部分代码)...\n  // 下面代码用于检测是否有新的更新任务\n  // 比如在componentDidMount函数中, 再次调用setState()\n\n  // 1. 检测常规(异步)任务, 如果有则会发起异步调度(调度中心`scheduler`只能异步调用)\n  ensureRootIsScheduled(root, now());\n  // 2. 检测同步任务, 如果有则主动调用flushSyncCallbackQueue(无需再次等待scheduler调度), 再次进入fiber树构造循环\n  flushSyncCallbackQueue();\n\n  return null;\n}\n```\n\n`commitRootImpl`函数中, 可以根据是否调用渲染, 把整个`commitRootImpl`分为 3 段(分别是`渲染前`, `渲染`, `渲染后`).\n\n### 渲染前\n\n为接下来正式渲染, 做一些准备工作. 主要包括:\n\n1. 设置全局状态(如: 更新`fiberRoot`上的属性)\n2. 重置全局变量(如: `workInProgressRoot`, `workInProgress`等)\n3. 再次更新副作用队列: 只针对根节点`fiberRoot.finishedWork`\n   - 默认情况下根节点的副作用队列是不包括自身的, 如果根节点有副作用, 则将根节点添加到副作用队列的末尾\n   - 注意只是延长了副作用队列, 但是`fiberRoot.lastEffect`指针并没有改变.\n     比如首次构造时, 根节点拥有`Snapshot`标记:\n\n![](../../snapshots/fibertree-commit/fiber-effectlist.png)\n\n### 渲染\n\n`commitRootImpl`函数中, 渲染阶段的主要逻辑是处理副作用队列, 将最新的 DOM 节点(已经在内存中, 只是还没渲染)渲染到界面上.\n\n整个渲染过程被分为 3 个函数分布实现:\n\n1. `commitBeforeMutationEffects`\n   - dom 变更之前, 处理副作用队列中带有`Snapshot`,`Passive`标记的`fiber`节点.\n2. `commitMutationEffects`\n   - dom 变更, 界面得到更新. 处理副作用队列中带有`Placement`, `Update`, `Deletion`, `Hydrating`标记的`fiber`节点.\n3. `commitLayoutEffects`\n   - dom 变更后, 处理副作用队列中带有`Update | Callback`标记的`fiber`节点.\n\n通过上述源码分析, 可以把`commitRootImpl`的职责概括为 2 个方面:\n\n1. 处理副作用队列. (步骤 1,2,3 都会处理, 只是处理节点的标识`fiber.flags`不同).\n2. 调用渲染器, 输出最终结果. (在步骤 2: `commitMutationEffects`中执行).\n\n所以`commitRootImpl`是处理`fiberRoot.finishedWork`这棵即将被渲染的`fiber`树, 理论上无需关心这棵`fiber`树是如何产生的(可以是`首次构造`产生, 也可以是`对比更新`产生). 为了清晰简便, 在下文的所有图示都使用`初次创建的fiber树结构`来进行演示.\n\n这 3 个函数处理的对象是`副作用队列`和`DOM对象`.\n\n所以无论`fiber树`结构有多么复杂, 到了`commitRoot`阶段, 实际起作用的只有 2 个节点:\n\n- `副作用队列`所在节点: 根节点, 即`HostRootFiber`节点.\n- `DOM对象`所在节点: 从上至下首个`HostComponent`类型的`fiber`节点, 此节点 \b`fiber.stateNode`实际上指向最新的 DOM 树.\n\n下图为了清晰, 省略了一些无关引用, 只留下`commitRoot`阶段实际会用到的`fiber`节点:\n\n![](../../snapshots/fibertree-commit/fiber-noredundant.png)\n\n#### commitBeforeMutationEffects\n\n第一阶段: dom 变更之前, 处理副作用队列中带有`Snapshot`,`Passive`标记的`fiber`节点.\n\n```js\n// ... 省略部分无关代码\nfunction commitBeforeMutationEffects() {\n  while (nextEffect !== null) {\n    const current = nextEffect.alternate;\n    const flags = nextEffect.flags;\n    // 处理`Snapshot`标记\n    if ((flags & Snapshot) !== NoFlags) {\n      commitBeforeMutationEffectOnFiber(current, nextEffect);\n    }\n    // 处理`Passive`标记\n    if ((flags & Passive) !== NoFlags) {\n      // Passive标记只在使用了hook, useEffect会出现. 所以此处是针对hook对象的处理\n      if (!rootDoesHavePassiveEffects) {\n        rootDoesHavePassiveEffects = true;\n        scheduleCallback(NormalSchedulerPriority, () => {\n          flushPassiveEffects();\n          return null;\n        });\n      }\n    }\n    nextEffect = nextEffect.nextEffect;\n  }\n}\n```\n\n注意：`commitBeforeMutationEffectOnFiber`实际上对应了`commitBeforeMutationLifeCycles`函数，在导入时进行了重命名\n\n1. 处理`Snapshot`标记\n\n```js\nfunction commitBeforeMutationLifeCycles(\n  current: Fiber | null,\n  finishedWork: Fiber,\n): void {\n  switch (finishedWork.tag) {\n    case FunctionComponent:\n    case ForwardRef:\n    case SimpleMemoComponent:\n    case Block: {\n      return;\n    }\n    case ClassComponent: {\n      if (finishedWork.flags & Snapshot) {\n        if (current !== null) {\n          const prevProps = current.memoizedProps;\n          const prevState = current.memoizedState;\n          const instance = finishedWork.stateNode;\n\n          const snapshot = instance.getSnapshotBeforeUpdate(\n            finishedWork.elementType === finishedWork.type\n              ? prevProps\n              : resolveDefaultProps(finishedWork.type, prevProps),\n            prevState,\n          );\n          instance.__reactInternalSnapshotBeforeUpdate = snapshot;\n        }\n      }\n      return;\n    }\n    case HostRoot: {\n      if (supportsMutation) {\n        if (finishedWork.flags & Snapshot) {\n          const root = finishedWork.stateNode;\n          clearContainer(root.containerInfo);\n        }\n      }\n      return;\n    }\n    case HostComponent:\n    case HostText:\n    case HostPortal:\n    case IncompleteClassComponent:\n      return;\n  }\n}\n```\n\n从源码中可以看到, 与`Snapshot`标记相关的类型只有`ClassComponent`和`HostRoot`.\n\n- 对于`ClassComponent`类型节点, 调用了`instance.getSnapshotBeforeUpdate`生命周期函数\n- 对于`HostRoot`类型节点, 调用`clearContainer`清空了容器节点(即`div#root`这个 dom 节点).\n\n2. 处理`Passive`标记\n\n`Passive`标记只会在使用了`hook`对象的`function`类型的节点上存在, 后续的执行过程在`hook原理`章节中详细说明. 此处我们需要了解在`commitRoot`的第一个阶段, 为了处理`hook`对象(如`useEffect`), 通过`scheduleCallback`单独注册了一个调度任务`task`, 等待调度中心`scheduler`处理.\n\n注意: 通过调度中心`scheduler`调度的任务`task`均是通过`MessageChannel`触发, 都是异步执行(可参考[React 调度原理(scheduler)](./scheduler.md)).\n\n小测试:\n\n```js\n// 以下示例代码中的输出顺序为 1, 3, 4, 2\nfunction Test() {\n  console.log(1);\n  useEffect(() => {\n    console.log(2);\n  });\n  console.log(3);\n  Promise.resolve(() => {\n    console.log(4);\n  });\n  return <div>test</div>;\n}\n```\n\n#### commitMutationEffects\n\n第二阶段: dom 变更, 界面得到更新. 处理副作用队列中带有`ContentReset`, `Ref`, `Placement`, `Update`, `Deletion`, `Hydrating`标记的`fiber`节点.\n\n```js\n// ...省略部分无关代码\nfunction commitMutationEffects(\n  root: FiberRoot,\n  renderPriorityLevel: ReactPriorityLevel,\n) {\n  // 处理Ref\n  if (flags & Ref) {\n    const current = nextEffect.alternate;\n    if (current !== null) {\n      // 先清空ref, 在commitRoot的第三阶段(dom变更后), 再重新赋值\n      commitDetachRef(current);\n    }\n  }\n  // 处理DOM突变\n  while (nextEffect !== null) {\n    const flags = nextEffect.flags;\n    const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);\n    switch (primaryFlags) {\n      case Placement: {\n        // 新增节点\n        commitPlacement(nextEffect);\n        nextEffect.flags &= ~Placement; // 注意Placement标记会被清除\n        break;\n      }\n      case PlacementAndUpdate: {\n        // Placement\n        commitPlacement(nextEffect);\n        nextEffect.flags &= ~Placement;\n        // Update\n        const current = nextEffect.alternate;\n        commitWork(current, nextEffect);\n        break;\n      }\n      case Update: {\n        // 更新节点\n        const current = nextEffect.alternate;\n        commitWork(current, nextEffect);\n        break;\n      }\n      case Deletion: {\n        // 删除节点\n        commitDeletion(root, nextEffect, renderPriorityLevel);\n        break;\n      }\n    }\n    nextEffect = nextEffect.nextEffect;\n  }\n}\n```\n\n处理 DOM 突变:\n\n1. `新增`: 函数调用栈 `commitPlacement` -> `insertOrAppendPlacementNode` -> `appendChild`\n2. `更新`: 函数调用栈 `commitWork` -> `commitUpdate`\n3. `删除`: 函数调用栈 `commitDeletion` -> `removeChild`\n\n最终会调用`appendChild, commitUpdate, removeChild`这些`react-dom`包中的函数. 它们是[`HostConfig`协议](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/README.md#practical-examples)([源码在 ReactDOMHostConfig.js 中](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMHostConfig.js))中规定的标准函数, 在渲染器`react-dom`包中进行实现. 这些函数就是直接操作 DOM, 所以执行之后, 界面也会得到更新.\n\n注意: `commitMutationEffects`执行之后, 在`commitRootImpl`函数中切换当前`fiber`树(`root.current = finishedWork`),保证`fiberRoot.current`指向代表当前界面的`fiber树`.\n\n![](../../snapshots/fibertree-commit/fiber-switch.png)\n\n#### commitLayoutEffects\n\n第三阶段: dom 变更后, 处理副作用队列中带有`Update, Callback, Ref`标记的`fiber`节点.\n\n```js\n// ...省略部分无关代码\nfunction commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {\n  while (nextEffect !== null) {\n    const flags = nextEffect.flags;\n    // 处理 Update和Callback标记\n    if (flags & (Update | Callback)) {\n      const current = nextEffect.alternate;\n      commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);\n    }\n    if (flags & Ref) {\n      // 重新设置ref\n      commitAttachRef(nextEffect);\n    }\n    nextEffect = nextEffect.nextEffect;\n  }\n}\n```\n\n核心逻辑都在`commitLayoutEffectOnFiber->commitLifeCycles`函数中.\n\n```js\n// ...省略部分无关代码\nfunction commitLifeCycles(\n  finishedRoot: FiberRoot,\n  current: Fiber | null,\n  finishedWork: Fiber,\n  committedLanes: Lanes,\n): void {\n  switch (finishedWork.tag) {\n    case ClassComponent: {\n      const instance = finishedWork.stateNode;\n      if (finishedWork.flags & Update) {\n        if (current === null) {\n          // 初次渲染: 调用 componentDidMount\n          instance.componentDidMount();\n        } else {\n          const prevProps =\n            finishedWork.elementType === finishedWork.type\n              ? current.memoizedProps\n              : resolveDefaultProps(finishedWork.type, current.memoizedProps);\n          const prevState = current.memoizedState;\n          // 更新阶段: 调用 componentDidUpdate\n          instance.componentDidUpdate(\n            prevProps,\n            prevState,\n            instance.__reactInternalSnapshotBeforeUpdate,\n          );\n        }\n      }\n      const updateQueue: UpdateQueue<*> | null =\n        (finishedWork.updateQueue: any);\n      if (updateQueue !== null) {\n        // 处理update回调函数 如: this.setState({}, callback)\n        commitUpdateQueue(finishedWork, updateQueue, instance);\n      }\n      return;\n    }\n    case HostComponent: {\n      const instance: Instance = finishedWork.stateNode;\n      if (current === null && finishedWork.flags & Update) {\n        const type = finishedWork.type;\n        const props = finishedWork.memoizedProps;\n        // 设置focus等原生状态\n        commitMount(instance, type, props, finishedWork);\n      }\n      return;\n    }\n  }\n}\n```\n\n在`commitLifeCycles`函数中:\n\n- 对于`ClassComponent`节点, 调用生命周期函数`componentDidMount`或`componentDidUpdate`, 调用`update.callback`回调函数.\n- 对于`HostComponent`节点, 如有`Update`标记, 需要设置一些原生状态(如: `focus`等)\n\n### 渲染后\n\n执行完上述步骤之后, 本次渲染任务就已经完成了. 在渲染完成后, 需要做一些重置和清理工作:\n\n1. 清除副作用队列\n\n   - 由于副作用队列是一个链表, 由于单个`fiber`对象的引用关系, 无法被`gc回收`.\n   - 将链表全部拆开, 当`fiber`对象不再使用的时候, 可以被`gc回收`.\n\n![](../../snapshots/fibertree-commit/clear-effectlist.png)\n\n2. 检测更新\n   - 在整个渲染过程中, 有可能产生新的`update`(比如在`componentDidMount`函数中, 再次调用`setState()`).\n   - 如果是常规(异步)任务, 不用特殊处理, 调用`ensureRootIsScheduled`确保任务已经注册到调度中心即可.\n   - 如果是同步任务, 则主动调用`flushSyncCallbackQueue`(无需再次等待 scheduler 调度), 再次进入 fiber 树构造循环\n\n```js\n// 清除副作用队列\nif (rootDoesHavePassiveEffects) {\n  // 有被动作用(使用useEffect), 保存一些全局变量\n} else {\n  // 分解副作用队列链表, 辅助垃圾回收.\n  // 如果有被动作用(使用useEffect), 会把分解操作放在flushPassiveEffects函数中\n  nextEffect = firstEffect;\n  while (nextEffect !== null) {\n    const nextNextEffect = nextEffect.nextEffect;\n    nextEffect.nextEffect = null;\n    if (nextEffect.flags & Deletion) {\n      detachFiberAfterEffects(nextEffect);\n    }\n    nextEffect = nextNextEffect;\n  }\n}\n// 重置一些全局变量(省略这部分代码)...\n// 下面代码用于检测是否有新的更新任务\n// 比如在componentDidMount函数中, 再次调用setState()\n\n// 1. 检测常规(异步)任务, 如果有则会发起异步调度(调度中心`scheduler`只能异步调用)\nensureRootIsScheduled(root, now());\n// 2. 检测同步任务, 如果有则主动调用flushSyncCallbackQueue(无需再次等待scheduler调度), 再次进入fiber树构造循环\nflushSyncCallbackQueue();\n```\n\n## 总结\n\n本节分析了`fiber 树渲染`的处理过程, 从宏观上看`fiber 树渲染`位于`reconciler 运作流程`中的输出阶段, 是整个`reconciler 运作流程`的链路中最后一环(从输入到输出). 本节根据源码, 具体从`渲染前, 渲染, 渲染后`三个方面分解了`commitRootImpl`函数. 其中最核心的`渲染`逻辑又分为了 3 个函数, 这 3 个函数共同处理了有副作用`fiber`节点, 并通过渲染器`react-dom`把最新的 DOM 对象渲染到界面上.\n"
  },
  {
    "path": "docs/main/fibertree-create.md",
    "content": "---\ntitle: fiber 树构造(初次创建)\ngroup: 运行核心\norder: 5\n---\n\n# fiber 树构造(初次创建)\n\n本节的内容完全建立在前文[fiber 树构造(基础准备)](./fibertree-prepare.md)中介绍的基础知识之上, 其中总结了`fiber 树构造`的 2 种情况:\n\n1. 初次创建: 在`React`应用首次启动时, 界面还没有渲染, 此时并不会进入对比过程, 相当于直接构造一棵全新的树.\n2. 对比更新: `React`应用启动后, 界面已经渲染. 如果再次发生更新, 创建`新fiber`之前需要和`旧fiber`进行对比. 最后构造的 fiber 树有可能是全新的, 也可能是部分更新的.\n\n本节只讨论`初次创建`这种情况, 为了控制篇幅(本节直击核心源码, 不再介绍基础知识, 可参照[fiber 树构造(基础准备)](./fibertree-prepare.md))并突出`fiber 树构造`过程, 后文会在`Legacy`模式下进行分析(因为只讨论`fiber树构造`原理, `Concurrent`模式与`Legacy`没有区别).\n\n本节示例代码如下([codesandbox 地址](https://codesandbox.io/s/busy-jang-b26hy?file=/src/App.js)):\n\n```js\nclass App extends React.Component {\n  componentDidMount() {\n    console.log(`App Mount`);\n    console.log(`App 组件对应的fiber节点: `, this._reactInternals);\n  }\n  render() {\n    return (\n      <div className=\"app\">\n        <header>header</header>\n        <Content />\n      </div>\n    );\n  }\n}\n\nclass Content extends React.Component {\n  componentDidMount() {\n    console.log(`Content Mount`);\n    console.log(`Content 组件对应的fiber节点: `, this._reactInternals);\n  }\n  render() {\n    return (\n      <React.Fragment>\n        <p>1</p>\n        <p>2</p>\n      </React.Fragment>\n    );\n  }\n}\nexport default App;\n```\n\n## 启动阶段\n\n在前文[React 应用的启动过程](./bootstrap.md)中分析了 3 种启动模式的差异, 在进入`react-reconciler`包之前(调用`updateContainer`之前), 内存状态图如下:\n\n![](../../snapshots/bootstrap/process-legacy.png)\n\n根据这个结构, 可以在控制台中打出当前页面对应的`fiber`树(用于观察其结构):\n\n```js\ndocument.getElementById('root')._reactRootContainer._internalRoot.current;\n```\n\n然后进入`react-reconciler`包调用[updateContainer 函数](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberReconciler.old.js#L250-L321):\n\n```js\n// ... 省略了部分代码\nexport function updateContainer(\n  element: ReactNodeList,\n  container: OpaqueRoot,\n  parentComponent: ?React$Component<any, any>,\n  callback: ?Function,\n): Lane {\n  // 获取当前时间戳\n  const current = container.current;\n  const eventTime = requestEventTime();\n  // 1. 创建一个优先级变量(车道模型)\n  const lane = requestUpdateLane(current);\n\n  // 2. 根据车道优先级, 创建update对象, 并加入fiber.updateQueue.pending队列\n  const update = createUpdate(eventTime, lane);\n  update.payload = { element };\n  callback = callback === undefined ? null : callback;\n  if (callback !== null) {\n    update.callback = callback;\n  }\n  enqueueUpdate(current, update);\n\n  // 3. 进入reconciler运作流程中的`输入`环节\n  scheduleUpdateOnFiber(current, lane, eventTime);\n  return lane;\n}\n```\n\n由于`update`对象的创建, 此时的内存结构如下:\n\n![](../../snapshots/fibertree-create/update-container.png)\n\n注意: 最初的`ReactElement`对象`<App/>`被挂载到`HostRootFiber.updateQueue.shared.pending.payload.element`中, 后文`fiber树构造`过程中会再次变动.\n\n## 构造阶段\n\n为了突出构造过程,排除干扰,先把内存状态图中的`FiberRoot`和`HostRootFiber`单独提出来(后文在此基础上添加):\n\n![](./../../snapshots/fibertree-create/initial-status.png)\n\n在[scheduleUpdateOnFiber 函数](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619)中:\n\n```js\n// ...省略部分代码\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber,\n  lane: Lane,\n  eventTime: number,\n) {\n  // 标记优先级\n  const root = markUpdateLaneFromFiberToRoot(fiber, lane);\n  if (lane === SyncLane) {\n    if (\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      // 首次渲染, 直接进行`fiber构造`\n      performSyncWorkOnRoot(root);\n    }\n    // ...\n  }\n}\n```\n\n可以看到, 在`Legacy`模式下且首次渲染时, 有 2 个函数[markUpdateLaneFromFiberToRoot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L625-L667)和[performSyncWorkOnRoot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L965-L1045).\n\n其中`markUpdateLaneFromFiberToRoot(fiber, lane)`函数在`fiber树构造(对比更新)`中才会发挥作用, 因为在`初次创建`时并没有与当前页面所对应的`fiber树`, 所以核心代码并没有执行, 最后直接返回了`FiberRoot`对象.\n\n`performSyncWorkOnRoot`看起来源码很多, `初次创建`中真正用到的就 2 个函数:\n\n```js\nfunction performSyncWorkOnRoot(root) {\n  let lanes;\n  let exitStatus;\n  if (\n    root === workInProgressRoot &&\n    includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)\n  ) {\n    // 初次构造时(因为root=fiberRoot, workInProgressRoot=null), 所以不会进入\n  } else {\n    // 1. 获取本次render的优先级, 初次构造返回 NoLanes\n    lanes = getNextLanes(root, NoLanes);\n    // 2. 从root节点开始, 至上而下更新\n    exitStatus = renderRootSync(root, lanes);\n  }\n\n  // 将最新的fiber树挂载到root.finishedWork节点上\n  const finishedWork: Fiber = (root.current.alternate: any);\n  root.finishedWork = finishedWork;\n  root.finishedLanes = lanes;\n  // 进入commit阶段\n  commitRoot(root);\n\n  // ...后面的内容本节不讨论\n}\n```\n\n其中`getNextLanes`返回本次 render 的渲染优先级(详见[fiber 树构造(基础准备)](./fibertree-prepare.md#优先级)中`优先级`相关小节)\n\n[renderRootSync](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1490-L1553)\n\n```js\nfunction renderRootSync(root: FiberRoot, lanes: Lanes) {\n  const prevExecutionContext = executionContext;\n  executionContext |= RenderContext;\n  // 如果fiberRoot变动, 或者update.lane变动, 都会刷新栈帧, 丢弃上一次渲染进度\n  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {\n    // 刷新栈帧, legacy模式下都会进入\n    prepareFreshStack(root, lanes);\n  }\n  do {\n    try {\n      workLoopSync();\n      break;\n    } catch (thrownValue) {\n      handleError(root, thrownValue);\n    }\n  } while (true);\n  executionContext = prevExecutionContext;\n  // 重置全局变量, 表明render结束\n  workInProgressRoot = null;\n  workInProgressRootRenderLanes = NoLanes;\n  return workInProgressRootExitStatus;\n}\n```\n\n在`renderRootSync`中, 在执行`fiber树构造`前(`workLoopSync`)会先刷新栈帧`prepareFreshStack`(参考[fiber 树构造(基础准备)](./fibertree-prepare.md#栈帧管理)).在这里创建了`HostRootFiber.alternate`, 重置全局变量`workInProgress`和`workInProgressRoot`等.\n\n![](./../../snapshots/fibertree-create/status-freshstack.png)\n\n### 循环构造\n\n逻辑来到`workLoopSync`, 虽然本节在`Legacy`模式下进行讨论, 此处还是对比一下`workLoopConcurrent`\n\n```js\nfunction workLoopSync() {\n  while (workInProgress !== null) {\n    performUnitOfWork(workInProgress);\n  }\n}\n\nfunction workLoopConcurrent() {\n  // Perform work until Scheduler asks us to yield\n  while (workInProgress !== null && !shouldYield()) {\n    performUnitOfWork(workInProgress);\n  }\n}\n```\n\n可以看到`workLoopConcurrent`相比于`Sync`, 会多一个停顿机制, 这个机制实现了`时间切片`和`可中断渲染`(参考[React 调度原理](./scheduler.md#时间切片原理))\n\n结合`performUnitOfWork函数`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1642-L1668))\n\n```js\n// ... 省略部分无关代码\nfunction performUnitOfWork(unitOfWork: Fiber): void {\n  // unitOfWork就是被传入的workInProgress\n  const current = unitOfWork.alternate;\n  let next;\n  next = beginWork(current, unitOfWork, subtreeRenderLanes);\n  unitOfWork.memoizedProps = unitOfWork.pendingProps;\n  if (next === null) {\n    // 如果没有派生出新的节点, 则进入completeWork阶段, 传入的是当前unitOfWork\n    completeUnitOfWork(unitOfWork);\n  } else {\n    workInProgress = next;\n  }\n}\n```\n\n可以明显的看出, 整个`fiber树构造`是一个深度优先遍历(可参考[React 算法之深度优先遍历](../algorithm/dfs.md)), 其中有 2 个重要的变量`workInProgress`和`current`(可参考前文[fiber 树构造(基础准备)](./fibertree-prepare.md#双缓冲技术)中介绍的`双缓冲技术`):\n\n- `workInProgress`和`current`都视为指针\n- `workInProgress`指向当前正在构造的`fiber`节点\n- `current = workInProgress.alternate`(即`fiber.alternate`), 指向当前页面正在使用的`fiber`节点. 初次构造时, 页面还未渲染, 此时`current = null`.\n\n在深度优先遍历中, 每个`fiber`节点都会经历 2 个阶段:\n\n1. 探寻阶段 `beginWork`\n2. 回溯阶段 `completeWork`\n\n这 2 个阶段共同完成了每一个`fiber`节点的创建, 所有`fiber`节点则构成了`fiber树`.\n\n### 探寻阶段 beginWork\n\n`beginWork(current, unitOfWork, subtreeRenderLanes)`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3083-L3494))针对所有的 Fiber 类型, 其中的每一个 case 处理一种 Fiber 类型. `updateXXX`函数(如: `updateHostRoot`, `updateClassComponent` 等)的主要逻辑:\n\n1. 根据 `ReactElement`对象创建所有的`fiber`节点, 最终构造出`fiber树形结构`(设置`return`和`sibling`指针)\n2. 设置`fiber.flags`(二进制形式变量, 用来标记 `fiber`节点 的`增,删,改`状态, 等待`completeWork阶段处理`)\n3. 设置`fiber.stateNode`局部状态(如`Class类型`节点: `fiber.stateNode=new Class()`)\n\n```js\nfunction beginWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const updateLanes = workInProgress.lanes;\n  if (current !== null) {\n    // update逻辑, 首次render不会进入\n  } else {\n    didReceiveUpdate = false;\n  }\n  // 1. 设置workInProgress优先级为NoLanes(最高优先级)\n  workInProgress.lanes = NoLanes;\n  // 2. 根据workInProgress节点的类型, 用不同的方法派生出子节点\n  switch (\n    workInProgress.tag // 只保留了本例使用到的case\n  ) {\n    case ClassComponent: {\n      const Component = workInProgress.type;\n      const unresolvedProps = workInProgress.pendingProps;\n      const resolvedProps =\n        workInProgress.elementType === Component\n          ? unresolvedProps\n          : resolveDefaultProps(Component, unresolvedProps);\n      return updateClassComponent(\n        current,\n        workInProgress,\n        Component,\n        resolvedProps,\n        renderLanes,\n      );\n    }\n    case HostRoot:\n      return updateHostRoot(current, workInProgress, renderLanes);\n    case HostComponent:\n      return updateHostComponent(current, workInProgress, renderLanes);\n    case HostText:\n      return updateHostText(current, workInProgress);\n    case Fragment:\n      return updateFragment(current, workInProgress, renderLanes);\n  }\n}\n```\n\n`updateXXX`函数(如: updateHostRoot, updateClassComponent 等)虽然 case 较多, 但是主要逻辑可以概括为 3 个步骤:\n\n1. 根据`fiber.pendingProps, fiber.updateQueue`等`输入数据`状态, 计算`fiber.memoizedState`作为`输出状态`\n2. 获取下级`ReactElement`对象\n   1. class 类型的 `fiber` 节点\n      - 构建`React.Component`实例\n      - 把新实例挂载到`fiber.stateNode`上\n      - 执行`render`之前的生命周期函数\n      - 执行`render`方法, 获取下级`reactElement`\n      - 根据实际情况, 设置`fiber.flags`\n   2. function 类型的 `fiber` 节点\n      - 执行 function, 获取下级`reactElement`\n      - 根据实际情况, 设置`fiber.flags`\n   3. HostComponent 类型(如: `div, span, button` 等)的 `fiber` 节点\n      - `pendingProps.children`作为下级`reactElement`\n      - 如果下级节点是文本节点,则设置下级节点为 null. 准备进入`completeUnitOfWork`阶段\n      - 根据实际情况, 设置`fiber.flags`\n   4. 其他类型...\n3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)\n   - 根据实际情况, 设置`fiber.flags`\n\n不同的`updateXXX`函数处理的`fiber`节点类型不同, 总的目的是为了向下生成子节点. 在这个过程中把一些需要持久化的数据挂载到`fiber`节点上(如`fiber.stateNode`,`fiber.memoizedState`等); 把`fiber`节点的特殊操作设置到`fiber.flags`(如:`节点ref`,`class组件的生命周期`,`function组件的hook`,`节点删除`等).\n\n这里列出`updateHostRoot`, `updateHostComponent`的代码, 对于其他常用 case 的分析(如`class`类型, `function`类型), 在`状态组件`章节中进行探讨.\n\n`fiber树`的根节点是`HostRootFiber`节点, 所以第一次进入`beginWork`会调用[updateHostRoot(current, workInProgress, renderLanes)](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L1053-L1122)\n\n```js\n// 省略与本节无关代码\nfunction updateHostRoot(current, workInProgress, renderLanes) {\n  // 1. 状态计算, 更新整合到 workInProgress.memoizedState中来\n  const updateQueue = workInProgress.updateQueue;\n  const nextProps = workInProgress.pendingProps;\n  const prevState = workInProgress.memoizedState;\n  const prevChildren = prevState !== null ? prevState.element : null;\n  cloneUpdateQueue(current, workInProgress);\n  // 遍历updateQueue.shared.pending, 提取有足够优先级的update对象, 计算出最终的状态 workInProgress.memoizedState\n  processUpdateQueue(workInProgress, nextProps, null, renderLanes);\n  const nextState = workInProgress.memoizedState;\n  // 2. 获取下级`ReactElement`对象\n  const nextChildren = nextState.element;\n  const root: FiberRoot = workInProgress.stateNode;\n  if (root.hydrate && enterHydrationState(workInProgress)) {\n    // ...服务端渲染相关, 此处省略\n  } else {\n    // 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)\n    reconcileChildren(current, workInProgress, nextChildren, renderLanes);\n  }\n  return workInProgress.child;\n}\n```\n\n普通 DOM 标签类型的节点(如`div`,`span`,`p`),会进入[updateHostComponent](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L1124-L1157):\n\n```js\n// ...省略部分无关代码\nfunction updateHostComponent(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n) {\n  // 1. 状态计算, 由于HostComponent是无状态组件, 所以只需要收集 nextProps即可, 它没有 memoizedState\n  const type = workInProgress.type;\n  const nextProps = workInProgress.pendingProps;\n  const prevProps = current !== null ? current.memoizedProps : null;\n  // 2. 获取下级`ReactElement`对象\n  let nextChildren = nextProps.children;\n  const isDirectTextChild = shouldSetTextContent(type, nextProps);\n\n  if (isDirectTextChild) {\n    // 如果子节点只有一个文本节点, 不用再创建一个HostText类型的fiber\n    nextChildren = null;\n  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {\n    // 特殊操作需要设置fiber.flags\n    workInProgress.flags |= ContentReset;\n  }\n  // 特殊操作需要设置fiber.flags\n  markRef(current, workInProgress);\n  // 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)\n  reconcileChildren(current, workInProgress, nextChildren, renderLanes);\n  return workInProgress.child;\n}\n```\n\n### 回溯阶段 completeWork\n\n`completeUnitOfWork(unitOfWork)`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1670-L1802)), 处理 `beginWork` 阶段已经创建出来的 `fiber` 节点, 核心逻辑:\n\n1. 调用`completeWork`\n   - 给`fiber`节点(tag=HostComponent, HostText)创建 DOM 实例, 设置`fiber.stateNode`局部状态(如`tag=HostComponent, HostText`节点: fiber.stateNode 指向这个 DOM 实例).\n   - 为 DOM 节点设置属性, 绑定事件(这里先说明有这个步骤, 详细的事件处理流程, 在`合成事件原理`中详细说明).\n   - 设置`fiber.flags`标记\n2. 把当前 `fiber` 对象的副作用队列(`firstEffect`和`lastEffect`)添加到父节点的副作用队列之后, 更新父节点的`firstEffect`和`lastEffect`指针.\n3. 识别`beginWork`阶段设置的`fiber.flags`, 判断当前 `fiber` 是否有副作用(增,删,改), 如果有, 需要将当前 `fiber` 加入到父节点的`effects`队列, 等待`commit`阶段处理.\n\n```js\nfunction completeUnitOfWork(unitOfWork: Fiber): void {\n  let completedWork = unitOfWork;\n  // 外层循环控制并移动指针(`workInProgress`,`completedWork`等)\n  do {\n    const current = completedWork.alternate;\n    const returnFiber = completedWork.return;\n    if ((completedWork.flags & Incomplete) === NoFlags) {\n      let next;\n      // 1. 处理Fiber节点, 会调用渲染器(调用react-dom包, 关联Fiber节点和dom对象, 绑定事件等)\n      next = completeWork(current, completedWork, subtreeRenderLanes); // 处理单个节点\n      if (next !== null) {\n        // 如果派生出其他的子节点, 则回到`beginWork`阶段进行处理\n        workInProgress = next;\n        return;\n      }\n      // 重置子节点的优先级\n      resetChildLanes(completedWork);\n      if (\n        returnFiber !== null &&\n        (returnFiber.flags & Incomplete) === NoFlags\n      ) {\n        // 2. 收集当前Fiber节点以及其子树的副作用effects\n        // 2.1 把子节点的副作用队列添加到父节点上\n        if (returnFiber.firstEffect === null) {\n          returnFiber.firstEffect = completedWork.firstEffect;\n        }\n        if (completedWork.lastEffect !== null) {\n          if (returnFiber.lastEffect !== null) {\n            returnFiber.lastEffect.nextEffect = completedWork.firstEffect;\n          }\n          returnFiber.lastEffect = completedWork.lastEffect;\n        }\n        // 2.2 如果当前fiber节点有副作用, 将其添加到子节点的副作用队列之后.\n        const flags = completedWork.flags;\n        if (flags > PerformedWork) {\n          // PerformedWork是提供给 React DevTools读取的, 所以略过PerformedWork\n          if (returnFiber.lastEffect !== null) {\n            returnFiber.lastEffect.nextEffect = completedWork;\n          } else {\n            returnFiber.firstEffect = completedWork;\n          }\n          returnFiber.lastEffect = completedWork;\n        }\n      }\n    } else {\n      // 异常处理, 本节不讨论\n    }\n\n    const siblingFiber = completedWork.sibling;\n    if (siblingFiber !== null) {\n      // 如果有兄弟节点, 返回之后再次进入`beginWork`阶段\n      workInProgress = siblingFiber;\n      return;\n    }\n    // 移动指针, 指向下一个节点\n    completedWork = returnFiber;\n    workInProgress = completedWork;\n  } while (completedWork !== null);\n  // 已回溯到根节点, 设置workInProgressRootExitStatus = RootCompleted\n  if (workInProgressRootExitStatus === RootIncomplete) {\n    workInProgressRootExitStatus = RootCompleted;\n  }\n}\n```\n\n接下来分析`fiber`处理函数[completeWork](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.old.js#L645-L1289)\n\n```js\nfunction completeWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const newProps = workInProgress.pendingProps;\n  switch (workInProgress.tag) {\n    case ClassComponent: {\n      // Class类型不做处理\n      return null;\n    }\n    case HostRoot: {\n      const fiberRoot = (workInProgress.stateNode: FiberRoot);\n      if (fiberRoot.pendingContext) {\n        fiberRoot.context = fiberRoot.pendingContext;\n        fiberRoot.pendingContext = null;\n      }\n      if (current === null || current.child === null) {\n         // 设置fiber.flags标记\n         workInProgress.flags |= Snapshot;\n      }\n      return null;\n    }\n    case HostComponent: {\n      popHostContext(workInProgress);\n      const rootContainerInstance = getRootHostContainer();\n      const type = workInProgress.type;\n      if (current !== null && workInProgress.stateNode != null) {\n        // update逻辑, 初次render不会进入\n      } else {\n        const currentHostContext = getHostContext();\n        // 1. 创建DOM对象\n        const instance = createInstance(\n          type,\n          newProps,\n          rootContainerInstance,\n          currentHostContext,\n          workInProgress,\n        );\n        // 2. 把子树中的DOM对象append到本节点的DOM对象之后\n        appendAllChildren(instance, workInProgress, false, false);\n        // 设置stateNode属性, 指向DOM对象\n        workInProgress.stateNode = instance;\n        if (\n          // 3. 设置DOM对象的属性, 绑定事件等\n          finalizeInitialChildren(\n            instance,\n            type,\n            newProps,\n            rootContainerInstance,\n            currentHostContext,\n          )\n        ) {\n          // 设置fiber.flags标记(Update)\n          markUpdate(workInProgress);\n        }\n        if (workInProgress.ref !== null) {\n          // 设置fiber.flags标记(Ref)\n          markRef(workInProgress);\n        }\n        return null;\n    }\n  }\n}\n```\n\n可以看到在满足条件的时候也会设置`fiber.flags`, 所以设置`fiber.flags`并非只在`beginWork`阶段.\n\n## 过程图解\n\n针对本节的示例代码, 将整个`fiber`树构造过程表示出来:\n\n构造前:\n\n在上文已经说明, 进入循环构造前会调用`prepareFreshStack`刷新栈帧, 在进入`fiber树构造`循环之前, 保持这这个初始化状态:\n\n![](../../snapshots/fibertree-create/unitofwork0.png)\n\n`performUnitOfWork`第 1 次调用(只执行`beginWork`):\n\n- 执行前: `workInProgress`指针指向`HostRootFiber.alternate`对象, 此时`current = workInProgress.alternate`指向`fiberRoot.current`是非空的(初次构造, 只在根节点时, `current`非空).\n- 执行过程: 调用`updateHostRoot`\n  - 在`reconcileChildren`阶段, 向下构造`次级子节点fiber(<App/>)`, 同时设置子节点(`fiber(<App/>)`)[fiber.flags |= Placement](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L376-L378)\n- 执行后: 返回下级节点`fiber(<App/>)`, 移动`workInProgress`指针指向子节点`fiber(<App/>)`\n\n![](../../snapshots/fibertree-create/unitofwork1.png)\n\n`performUnitOfWork`第 2 次调用(只执行`beginWork`):\n\n- 执行前: `workInProgress`指针指向`fiber(<App/>)`节点, 此时`current = null`\n- 执行过程: 调用`updateClassComponent`\n  - 本示例中, class 实例存在生命周期函数`componentDidMount`, 所以会设置`fiber(<App/>)`节点[workInProgress.flags |= Update](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L892-L894)\n  - 另外也会为了`React DevTools`能够识别状态组件的执行进度, 会设置[workInProgress.flags |= PerformedWork](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L379)(在`commit`阶段会排除这个`flag`, 此处只是列出`workInProgress.flags`的设置场景, 不讨论`React DevTools`)\n  - 需要注意`classInstance.render()`在本步骤执行后, 虽然返回了`render`方法中所有的`ReactElement`对象, 但是随后`reconcileChildren`只构造`次级子节点`\n  - 在`reconcileChildren`阶段, 向下构造`次级子节点div`\n- 执行后: 返回下级节点`fiber(div)`, 移动`workInProgress`指针指向子节点`fiber(div)`\n\n![](../../snapshots/fibertree-create/unitofwork2.png)\n\n`performUnitOfWork`第 3 次调用(只执行`beginWork`):\n\n- 执行前: `workInProgress`指针指向`fiber(div)`节点, 此时`current = null`\n- 执行过程: 调用`updateHostComponent`\n  - 在`reconcileChildren`阶段, 向下构造`次级子节点`(本示例中, `div`有 2 个次级子节点)\n- 执行后: 返回下级节点`fiber(header)`, 移动`workInProgress`指针指向子节点`fiber(header)`\n\n![](../../snapshots/fibertree-create/unitofwork3.png)\n\n`performUnitOfWork`第 4 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行前: `workInProgress`指针指向`fiber(header)`节点, 此时`current = null`\n- `beginWork`执行过程: 调用`updateHostComponent`\n  - 本示例中`header`的子节点是一个[直接文本节点](https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-dom/src/client/ReactDOMHostConfig.js#L350-L361),设置[nextChildren = null](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L1147)(直接文本节点并不会被当成具体的`fiber`节点进行处理, 而是在宿主环境(父组件)中通过属性进行设置. 所以无需创建`HostText`类型的 fiber 节点, 同时节省了向下遍历开销.).\n  - 由于`nextChildren = null`, 经过`reconcileChildren`阶段处理后, 返回值也是`null`\n- `beginWork`执行后: 由于下级节点为`null`, 所以进入`completeUnitOfWork(unitOfWork)`函数, 传入的参数`unitOfWork`实际上就是`workInProgress`(此时指向`fiber(header)`节点)\n\n![](../../snapshots/fibertree-create/unitofwork4.1.png)\n\n- `completeUnitOfWork`执行前: `workInProgress`指针指向`fiber(header)`节点\n- `completeUnitOfWork`执行过程: 以`fiber(header)`为起点, 向上回溯\n\n第 1 次循环:\n\n1. 执行`completeWork`函数\n   - 创建`fiber(header)`节点对应的`DOM`实例, 并`append`子节点的`DOM`实例\n   - 设置`DOM`属性, 绑定事件等(本示例中, 节点`fiber(header)`没有事件绑定)\n2. 上移副作用队列: 由于本节点`fiber(header)`没有副作用(`fiber.flags = 0`), 所以执行之后副作用队列没有实质变化(目前为空).\n3. 向上回溯: 由于还有兄弟节点, 把`workInProgress`指针指向下一个兄弟节点`fiber(<Content/>)`, 退出`completeUnitOfWork`.\n\n![](../../snapshots/fibertree-create/unitofwork4.2.png)\n\n`performUnitOfWork`第 5 次调用(执行`beginWork`):\n\n- 执行前:`workInProgress`指针指向`fiber(<Content/>)`节点.\n- 执行过程: 这是一个`class`类型的节点, 与第 2 次调用逻辑一致.\n- 执行后: 返回下级节点`fiber(p)`, 移动`workInProgress`指针指向子节点`fiber(p)`\n\n![](../../snapshots/fibertree-create/unitofwork5.png)\n\n`performUnitOfWork`第 6 次调用(执行`beginWork`和`completeUnitOfWork`):与第 4 次调用中创建`fiber(header)`节点的逻辑一致. 先后会执行`beginWork`和`completeUnitOfWork`, 最后构造 DOM 实例, 并将把`workInProgress`指针指向下一个兄弟节点`fiber(p)`.\n\n![](../../snapshots/fibertree-create/unitofwork6.png)\n\n`performUnitOfWork`第 7 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行过程: 与上次调用中创建`fiber(p)`节点的逻辑一致\n- `completeUnitOfWork`执行过程: 以`fiber(p)`为起点, 向上回溯\n\n第 1 次循环:\n\n1. 执行`completeWork`函数: 创建`fiber(p)`节点对应的`DOM`实例, 并`append`子树节点的`DOM`实例\n2. 上移副作用队列: 由于本节点`fiber(p)`没有副作用, 所以执行之后副作用队列没有实质变化(目前为空).\n3. 向上回溯: 由于没有兄弟节点, 把`workInProgress`指针指向父节点`fiber(<Content/>)`\n\n![](../../snapshots/fibertree-create/unitofwork7.png)\n\n第 2 次循环:\n\n1. 执行`completeWork`函数: class 类型的节点不做处理\n2. 上移副作用队列:\n   - 本节点`fiber(<Content/>)`的`flags`标志位有改动(`completedWork.flags > PerformedWork`), 将本节点添加到父节点(`fiber(div)`)的副作用队列之后(`firstEffect`和`lastEffect`属性分别指向副作用队列的首部和尾部).\n3. 向上回溯: 把`workInProgress`指针指向父节点`fiber(div)`\n\n![](../../snapshots/fibertree-create/unitofwork7.1.png)\n\n第 3 次循环:\n\n1. 执行`completeWork`函数: 创建`fiber(div)`节点对应的`DOM`实例, 并`append`子树节点的`DOM`实例\n2. 上移副作用队列:\n   - 本节点`fiber(div)`的副作用队列不为空, 将其拼接到父节点`fiber<App/>`的副作用队列后面.\n3. 向上回溯: 把`workInProgress`指针指向父节点`fiber(<App/>)`\n\n![](../../snapshots/fibertree-create/unitofwork7.2.png)\n\n第 4 次循环:\n\n1. 执行`completeWork`函数: class 类型的节点不做处理\n2. 上移副作用队列:\n   - 本节点`fiber(<App/>)`的副作用队列不为空, 将其拼接到父节点`fiber(HostRootFiber)`的副作用队列上.\n   - 本节点`fiber(<App/>)`的`flags`标志位有改动(`completedWork.flags > PerformedWork`), 将本节点添加到父节点`fiber(HostRootFiber)`的副作用队列之后.\n   - 最后队列的顺序是`子节点在前, 本节点在后`\n3. 向上回溯: 把`workInProgress`指针指向父节点`fiber(HostRootFiber)`\n\n![](../../snapshots/fibertree-create/unitofwork7.3.png)\n\n第 5 次循环:\n\n1. 执行`completeWork`函数: 对于`HostRoot`类型的节点, 初次构造时设置[workInProgress.flags |= Snapshot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.old.js#L693)\n2. 向上回溯: 由于父节点为空, 无需进入处理副作用队列的逻辑. 最后设置`workInProgress=null`, 并退出`completeUnitOfWork`\n\n![](../../snapshots/fibertree-create/unitofwork7.4.png)\n\n到此整个`fiber树构造循环`已经执行完毕, 拥有一棵完整的`fiber树`, 并且在`fiber树`的根节点上挂载了副作用队列, 副作用队列的顺序是层级越深子节点越靠前.\n\n`renderRootSync`函数退出之前, 会重置`workInProgressRoot = null`, 表明没有正在进行中的`render`. 且把最新的`fiber树`挂载到`fiberRoot.finishedWork`上. 这时整个 fiber 树的内存结构如下(注意`fiberRoot.finishedWork`和`fiberRoot.current`指针,在`commitRoot`阶段会进行处理):\n\n![](../../snapshots/fibertree-create/fibertree-beforecommit.png)\n\n## 总结\n\n本节演示了初次创建`fiber树`的全部过程, 跟踪了创建过程中内存引用的变化情况. `fiber树构造循环`负责构造新的`fiber`树, 构造过程中同时标记`fiber.flags`, 最终把所有被标记的`fiber`节点收集到一个副作用队列中, 这个副作用队列被挂载到根节点上(`HostRootFiber.alternate.firstEffect`). 此时的`fiber树`和与之对应的`DOM节点`都还在内存当中, 等待`commitRoot`阶段进行渲染.\n"
  },
  {
    "path": "docs/main/fibertree-prepare.md",
    "content": "---\ntitle: fiber 树构造(基础准备)\ngroup: 运行核心\norder: 4\n---\n\n# fiber 树构造(基础准备)\n\n在 React 运行时中, `fiber树构造`位于`react-reconciler`包.\n\n在正式解读`fiber树构造`之前, 再次回顾一下[reconciler 运作流程](./reconciler-workflow.md)的 4 个阶段:\n\n![](../../snapshots/reconciler-workflow/reactfiberworkloop.png)\n\n1. 输入阶段: 衔接`react-dom`包, 承接`fiber更新`请求(可以参考[React 应用的启动过程](./bootstrap.md)).\n2. 注册调度任务: 与调度中心(`scheduler`包)交互, 注册调度任务`task`, 等待任务回调(可以参考[React 调度原理(scheduler)](./scheduler.md)).\n3. 执行任务回调: 在内存中构造出`fiber树`和`DOM`对象, 也是**fiber 树构造的重点内容**.\n4. 输出: 与渲染器(`react-dom`)交互, 渲染`DOM`节点.\n\n`fiber树构造`处于上述第 3 个阶段, 可以通过不同的视角来理解`fiber树构造`在`React`运行时中所处的位置:\n\n- 从`scheduler`调度中心的角度来看, 它是任务队列`taskQueue`中的一个具体的任务回调(`task.callback`).\n- 从[React 工作循环](./workloop.md)的角度来看, 它属于`fiber树构造循环`.\n\n由于`fiber 树构造`源码量比较大, 本系列根据`React`运行的`内存状态`, 分为 2 种情况来说明:\n\n1. 初次创建: 在`React`应用首次启动时, 界面还没有渲染, 此时并不会进入对比过程, 相当于直接构造一棵全新的树.\n2. 对比更新: `React`应用启动后, 界面已经渲染. 如果再次发生更新, 创建`新fiber`之前需要和`旧fiber`进行对比. 最后构造的 fiber 树有可能是全新的, 也可能是部分更新的.\n\n无论是`初次创建`还是`对比更新`, 基础概念都是通用的, 本节将介绍这些基础知识, 为正式进入`fiber树构造`做准备.\n\n## ReactElement, Fiber, DOM 三者的关系\n\n在[React 应用中的高频对象](./object-structure.md)一文中, 已经介绍了`ReactElement`和`Fiber`对象的数据结构. 这里我们梳理出`ReactElement, Fiber, DOM`这 3 种对象的关系\n\n1. [ReactElement 对象](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactElement.js#L126-L146)(type 定义在[shared 包中](https://github.com/facebook/react/blob/v17.0.2/packages/shared/ReactElementType.js#L15))\n\n   - 所有采用`jsx`语法书写的节点, 都会被编译器转换, 最终会以`React.createElement(...)`的方式, 创建出来一个与之对应的`ReactElement`对象\n\n2. [fiber 对象](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiber.old.js#L116-L155)(type 类型的定义在[ReactInternalTypes.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactInternalTypes.js#L47-L174)中)\n\n   - `fiber对象`是通过`ReactElement`对象进行创建的, 多个`fiber对象`构成了一棵`fiber树`, `fiber树`是构造`DOM树`的数据模型, `fiber树`的任何改动, 最后都体现到`DOM树`.\n\n3. [DOM 对象](https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model): 文档对象模型\n   - `DOM`将文档解析为一个由节点和对象（包含属性和方法的对象）组成的结构集合, 也就是常说的`DOM树`.\n   - `JavaScript`可以访问和操作存储在 DOM 中的内容, 也就是操作`DOM对象`, 进而触发 UI 渲染.\n\n它们之间的关系反映了我们书写的 JSX 代码到 DOM 节点的转换过程:\n\n![](../../snapshots/fibertree-create/code2dom.png)\n\n注意:\n\n- 开发人员能够控制的是`JSX`, 也就是`ReactElement`对象.\n- `fiber树`是通过`ReactElement`生成的, 如果脱离了`ReactElement`,`fiber树`也无从谈起. 所以是`ReactElement`树(不是严格的树结构, 为了方便也称为树)驱动`fiber树`.\n- `fiber树`是`DOM树`的数据模型, `fiber树`驱动`DOM树`\n\n开发人员通过编程只能控制`ReactElement`树的结构, `ReactElement树`驱动`fiber树`, `fiber树`再驱动`DOM树`, 最后展现到页面上. 所以`fiber树`的构造过程, 实际上就是`ReactElement`对象到`fiber`对象的转换过程.\n\n## 全局变量\n\n从[React 工作循环](./workloop.md)的角度来看, 整个构造过程被包裹在`fiber树构造循环`中(对应源码位于[ReactFiberWorkLoop.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js)).\n\n在`React`运行时, `ReactFiberWorkLoop.js`闭包中的`全局变量`会随着`fiber树构造循环`的进行而变化, 现在查看其中重要的全局变量([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L247-L367)):\n\n```js\n// 当前React的执行栈(执行上下文)\nlet executionContext: ExecutionContext = NoContext;\n\n// 当前root节点\nlet workInProgressRoot: FiberRoot | null = null;\n// 正在处理中的fiber节点\nlet workInProgress: Fiber | null = null;\n// 正在渲染的车道(复数)\nlet workInProgressRootRenderLanes: Lanes = NoLanes;\n\n// 包含所有子节点的优先级, 是workInProgressRootRenderLanes的超集\n// 大多数情况下: 在工作循环整体层面会使用workInProgressRootRenderLanes, 在begin/complete阶段层面会使用 subtreeRenderLanes\nlet subtreeRenderLanes: Lanes = NoLanes;\n// 一个栈结构: 专门存储当前节点的 subtreeRenderLanes\nconst subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);\n\n// fiber构造完后, root节点的状态: completed, errored, suspended等\nlet workInProgressRootExitStatus: RootExitStatus = RootIncomplete;\n// 重大错误\nlet workInProgressRootFatalError: mixed = null;\n// 整个render期间所使用到的所有lanes\nlet workInProgressRootIncludedLanes: Lanes = NoLanes;\n// 在render期间被跳过(由于优先级不够)的lanes: 只包括未处理的updates, 不包括被复用的fiber节点\nlet workInProgressRootSkippedLanes: Lanes = NoLanes;\n// 在render期间被修改过的lanes\nlet workInProgressRootUpdatedLanes: Lanes = NoLanes;\n\n// 防止无限循环和嵌套更新\nconst NESTED_UPDATE_LIMIT = 50;\nlet nestedUpdateCount: number = 0;\nlet rootWithNestedUpdates: FiberRoot | null = null;\n\nconst NESTED_PASSIVE_UPDATE_LIMIT = 50;\nlet nestedPassiveUpdateCount: number = 0;\n\n// 发起更新的时间\nlet currentEventTime: number = NoTimestamp;\nlet currentEventWipLanes: Lanes = NoLanes;\nlet currentEventPendingLanes: Lanes = NoLanes;\n```\n\n在源码中, 大部分变量都带有英文注释(读者可自行查阅), 此处只列举了`fiber树构造循环`中最核心的变量\n\n### 执行上下文\n\n在全局变量中有`executionContext`, 代表`渲染期间`的`执行栈`(或叫做`执行上下文`), 它也是一个二进制表示的变量, 通过位运算进行操作(参考[React 算法之位运算](../algorithm/bitfield.md)). 在源码中一共定义了 8 种执行栈:\n\n```js\ntype ExecutionContext = number;\nexport const NoContext = /*             */ 0b0000000;\nconst BatchedContext = /*               */ 0b0000001;\nconst EventContext = /*                 */ 0b0000010;\nconst DiscreteEventContext = /*         */ 0b0000100;\nconst LegacyUnbatchedContext = /*       */ 0b0001000;\nconst RenderContext = /*                */ 0b0010000;\nconst CommitContext = /*                */ 0b0100000;\n```\n\n上文回顾了`reconciler 运作流程`的 4 个阶段, 这 4 个阶段只是一个整体划分. 如果具体到每一次更新, 是有差异的. 比如说: `Legacy`模式下的首次更新, 不会经过`调度中心`(第 2 阶段),而是直接进入`fiber树构造`(第 3 阶段).\n\n事实上正是`executionContext`在操控`reconciler 运作流程`(源码体现在[scheduleUpdateOnFiber 函数](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619)).\n\n```js\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber,\n  lane: Lane,\n  eventTime: number,\n) {\n  if (lane === SyncLane) {\n    // legacy或blocking模式\n    if (\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      performSyncWorkOnRoot(root);\n    } else {\n      // 后续的更新\n      // 进入第2阶段, 注册调度任务\n      ensureRootIsScheduled(root, eventTime);\n      if (executionContext === NoContext) {\n        // 如果执行上下文为空, 会取消调度任务, 手动执行回调\n        // 进入第3阶段, 进行fiber树构造\n        flushSyncCallbackQueue();\n      }\n    }\n  } else {\n    // concurrent模式\n    // 无论是否初次更新, 都正常进入第2阶段, 注册调度任务\n    ensureRootIsScheduled(root, eventTime);\n  }\n}\n```\n\n在 render 过程中, 每一个阶段都会改变`executionContext`(render 之前, 会设置`executionContext |= RenderContext`; commit 之前, 会设置`executionContext |= CommitContext`), 假设在`render`过程中再次发起更新(如在`UNSAFE_componentWillReceiveProps`生命周期中调用`setState`)则可通过`executionContext`来判断当前的`render`状态.\n\n### 双缓冲技术(double buffering)\n\n在全局变量中有`workInProgress`, 还有不少以`workInProgress`来命名的变量. `workInProgress`的应用实际上就是`React`的双缓冲技术(`double buffering`).\n\n在上文我们梳理了`ReactElement, Fiber, DOM三者的关系`, `fiber树`的构造过程, 就是把`ReactElement`转换成`fiber树`的过程. 在这个过程中, 内存里会同时存在 2 棵`fiber树`:\n\n- 其一: 代表当前界面的`fiber`树(已经被展示出来, 挂载到`fiberRoot.current`上). 如果是初次构造(`初始化渲染`), 页面还没有渲染, 此时界面对应的 fiber 树为空(`fiberRoot.current = null`).\n- 其二: 正在构造的`fiber`树(即将展示出来, 挂载到`HostRootFiber.alternate`上, 正在构造的节点称为`workInProgress`). 当构造完成之后, 重新渲染页面, 最后切换`fiberRoot.current = workInProgress`, 使得`fiberRoot.current`重新指向代表当前界面的`fiber`树.\n\n此处涉及到 2 个全局对象`fiberRoot`和`HostRootFiber`, 在[React 应用的启动过程](./bootstrap.md)中有详细的说明.\n\n用图来表述`double buffering`的概念如下:\n\n1. 构造过程中, `fiberRoot.current`指向当前界面对应的`fiber`树.\n\n![](../../snapshots/fibertree-create/fibertreecreate1-progress.png)\n\n2. 构造完成并渲染, 切换`fiberRoot.current`指针, 使其继续指向当前界面对应的`fiber`树(原来代表界面的 fiber 树, 变成了内存中).\n\n![](../../snapshots/fibertree-create/fibertreecreate2-complete.png)\n\n### 优先级 {#lanes}\n\n在全局变量中有不少变量都以 Lanes 命名(如`workInProgressRootRenderLanes`,`subtreeRenderLanes`其作用见上文注释), 它们都与优先级相关.\n\n在前文[React 中的优先级管理](./priority.md)中, 我们介绍了`React`中有 3 套优先级体系, 并了解了它们之间的关联. 现在`fiber树构造`过程中, 将要深入分析车道模型`Lane`的具体应用.\n\n在整个`react-reconciler`包中, `Lane`的应用可以分为 3 个方面:\n\n#### `update`优先级(update.lane) {#update-lane}\n\n在[React 应用中的高频对象](./object-structure.md#Update)一文中, 介绍过`update`对象, 它是一个环形链表. 对于单个`update`对象来讲, `update.lane`代表它的优先级, 称之为`update`优先级.\n\n观察其构造函数([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactUpdateQueue.old.js#L152-L163)),其优先级是由外界传入.\n\n```js\nexport function createUpdate(eventTime: number, lane: Lane): Update<*> {\n  const update: Update<*> = {\n    eventTime,\n    lane,\n    tag: UpdateState,\n    payload: null,\n    callback: null,\n    next: null,\n  };\n  return update;\n}\n```\n\n在`React`体系中, 有 2 种情况会创建`update`对象:\n\n1. 应用初始化: 在`react-reconciler`包中的`updateContainer`函数中([源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberReconciler.old.js#L250-L321))\n\n   ```js\n   export function updateContainer(\n     element: ReactNodeList,\n     container: OpaqueRoot,\n     parentComponent: ?React$Component<any, any>,\n     callback: ?Function,\n   ): Lane {\n     const current = container.current;\n     const eventTime = requestEventTime();\n     const lane = requestUpdateLane(current); // 根据当前时间, 创建一个update优先级\n     const update = createUpdate(eventTime, lane); // lane被用于创建update对象\n     update.payload = { element };\n     enqueueUpdate(current, update);\n     scheduleUpdateOnFiber(current, lane, eventTime);\n     return lane;\n   }\n   ```\n\n2. 发起组件更新: 假设在 class 组件中调用`setState`([源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L193-L288))\n\n```js\nconst classComponentUpdater = {\n  isMounted,\n  enqueueSetState(inst, payload, callback) {\n    const fiber = getInstance(inst);\n    const eventTime = requestEventTime(); // 根据当前时间, 创建一个update优先级\n    const lane = requestUpdateLane(fiber); // lane被用于创建update对象\n    const update = createUpdate(eventTime, lane);\n    update.payload = payload;\n    enqueueUpdate(fiber, update);\n    scheduleUpdateOnFiber(fiber, lane, eventTime);\n  },\n};\n```\n\n可以看到, 无论是`应用初始化`或者`发起组件更新`, 创建`update.lane`的逻辑都是一样的, 都是根据当前时间, 创建一个 update 优先级.\n\n[requestUpdateLane](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L392-L493):\n\n```js\nexport function requestUpdateLane(fiber: Fiber): Lane {\n  // Special cases\n  const mode = fiber.mode;\n  if ((mode & BlockingMode) === NoMode) {\n    // legacy 模式\n    return (SyncLane: Lane);\n  } else if ((mode & ConcurrentMode) === NoMode) {\n    // blocking模式\n    return getCurrentPriorityLevel() === ImmediateSchedulerPriority\n      ? (SyncLane: Lane)\n      : (SyncBatchedLane: Lane);\n  }\n  // concurrent模式\n  if (currentEventWipLanes === NoLanes) {\n    currentEventWipLanes = workInProgressRootIncludedLanes;\n  }\n  const isTransition = requestCurrentTransition() !== NoTransition;\n  if (isTransition) {\n    // 特殊情况, 处于suspense过程中\n    if (currentEventPendingLanes !== NoLanes) {\n      currentEventPendingLanes =\n        mostRecentlyUpdatedRoot !== null\n          ? mostRecentlyUpdatedRoot.pendingLanes\n          : NoLanes;\n    }\n    return findTransitionLane(currentEventWipLanes, currentEventPendingLanes);\n  }\n  // 正常情况, 获取调度优先级\n  const schedulerPriority = getCurrentPriorityLevel();\n  let lane;\n  if (\n    (executionContext & DiscreteEventContext) !== NoContext &&\n    schedulerPriority === UserBlockingSchedulerPriority\n  ) {\n    // executionContext 存在输入事件. 且调度优先级是用户阻塞性质\n    lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes);\n  } else {\n    // 调度优先级转换为车道模型\n    const schedulerLanePriority =\n      schedulerPriorityToLanePriority(schedulerPriority);\n    lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes);\n  }\n  return lane;\n}\n```\n\n可以看到`requestUpdateLane`的作用是返回一个合适的 update 优先级.\n\n1. legacy 模式: 返回`SyncLane`\n2. blocking 模式: 返回`SyncLane`\n3. concurrent 模式:\n   - 正常情况下, 根据当前的`调度优先级`来生成一个`lane`.\n   - 特殊情况下(处于 suspense 过程中), 会优先选择`TransitionLanes`通道中的空闲通道(如果所有`TransitionLanes`通道都被占用, 就取最高优先级. [源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L548-L563)).\n\n最后通过`scheduleUpdateOnFiber(current, lane, eventTime);`函数, 把`update.lane`正式带入到了`输入`阶段.\n\n`scheduleUpdateOnFiber`是`输入`阶段的必经函数, 在本系列的文章中已经多次提到, 此处以`update.lane`的视角分析:\n\n```js\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber,\n  lane: Lane,\n  eventTime: number,\n) {\n  if (lane === SyncLane) {\n    // legacy或blocking模式\n    if (\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      performSyncWorkOnRoot(root);\n    } else {\n      ensureRootIsScheduled(root, eventTime); // 注册回调任务\n      if (executionContext === NoContext) {\n        flushSyncCallbackQueue(); // 取消schedule调度 ,主动刷新回调队列,\n      }\n    }\n  } else {\n    // concurrent模式\n    ensureRootIsScheduled(root, eventTime);\n  }\n}\n```\n\n当`lane === SyncLane`也就是 legacy 或 blocking 模式中, 注册完回调任务之后(`ensureRootIsScheduled(root, eventTime)`), 如果执行上下文为空, 会取消 schedule 调度, 主动刷新回调队列`flushSyncCallbackQueue()`.\n\n这里包含了一个热点问题(`setState到底是同步还是异步`)的标准答案:\n\n- 如果逻辑进入`flushSyncCallbackQueue`(`executionContext === NoContext`), 则会主动取消调度, 并刷新回调, 立即进入`fiber树`构造过程. 当执行`setState`下一行代码时, `fiber树`已经重新渲染了, 故`setState`体现为同步.\n- 正常情况下, 不会取消`schedule调度`. 由于`schedule调度`是通过`MessageChannel`触发(宏任务), 故体现为异步.\n\n#### `渲染`优先级(renderLanes)\n\n这是一个全局概念, 每一次`render`之前, 首先要确定本次`render`的优先级. 具体对应到源码如下:\n\n```js\n// ...省略无关代码\nfunction performSyncWorkOnRoot(root) {\n  let lanes;\n  let exitStatus;\n  // 获取本次`render`的优先级\n  lanes = getNextLanes(root, lanes);\n  exitStatus = renderRootSync(root, lanes);\n}\n// ...省略无关代码\nfunction performConcurrentWorkOnRoot(root) {\n  // 获取本次`render`的优先级\n  let lanes = getNextLanes(\n    root,\n    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,\n  );\n  if (lanes === NoLanes) {\n    return null;\n  }\n  let exitStatus = renderRootConcurrent(root, lanes);\n}\n```\n\n可以看到, 无论是`Legacy`还是`Concurrent`模式, 在正式`render`之前, 都会调用`getNextLanes`获取一个优先级([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L249-L303)).\n\n```js\n// ...省略部分代码\nexport function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {\n  // 1. check是否有等待中的lanes\n  const pendingLanes = root.pendingLanes;\n  if (pendingLanes === NoLanes) {\n    return_highestLanePriority = NoLanePriority;\n    return NoLanes;\n  }\n  let nextLanes = NoLanes;\n  let nextLanePriority = NoLanePriority;\n  const expiredLanes = root.expiredLanes;\n  const suspendedLanes = root.suspendedLanes;\n  const pingedLanes = root.pingedLanes;\n  // 2. check是否有已过期的lanes\n  if (expiredLanes !== NoLanes) {\n    nextLanes = expiredLanes;\n    nextLanePriority = return_highestLanePriority = SyncLanePriority;\n  } else {\n    const nonIdlePendingLanes = pendingLanes & NonIdleLanes;\n    if (nonIdlePendingLanes !== NoLanes) {\n      // 非Idle任务 ...\n    } else {\n      // Idle任务 ...\n    }\n  }\n  if (nextLanes === NoLanes) {\n    return NoLanes;\n  }\n  return nextLanes;\n}\n```\n\n`getNextLanes`会根据`fiberRoot`对象上的属性(`expiredLanes`, `suspendedLanes`, `pingedLanes`等), 确定出当前最紧急的`lanes`.\n\n此处返回的`lanes`会作为全局渲染的优先级, 用于`fiber树构造过程`中. 针对`fiber对象`或`update对象`, 只要它们的优先级(如: `fiber.lanes`和`update.lane`)比`渲染优先级`低, 都将会被忽略.\n\n#### `fiber`优先级(fiber.lanes)\n\n在[React 应用中的高频对象](./object-structure.md)一文中, 介绍过`fiber`对象的数据结构. 其中有 2 个属性与优先级相关:\n\n1. `fiber.lanes`: 代表本节点的优先级\n2. `fiber.childLanes`: 代表子节点的优先级\n   从`FiberNode`的构造函数中可以看出, `fiber.lanes`和`fiber.childLanes`的初始值都为`NoLanes`, 在`fiber树构造`过程中, 使用全局的渲染优先级(`renderLanes`)和`fiber.lanes`判断`fiber`节点是否更新([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3121-L3296)).\n   - 如果全局的渲染优先级`renderLanes`不包括`fiber.lanes`, 证明该`fiber`节点没有更新, 可以复用.\n   - 如果不能复用, 进入创建阶段.\n\n```js\nfunction beginWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const updateLanes = workInProgress.lanes;\n  if (current !== null) {\n    const oldProps = current.memoizedProps;\n    const newProps = workInProgress.pendingProps;\n    if (\n      oldProps !== newProps ||\n      hasLegacyContextChanged() ||\n      // Force a re-render if the implementation changed due to hot reload:\n      (__DEV__ ? workInProgress.type !== current.type : false)\n    ) {\n      didReceiveUpdate = true;\n    } else if (!includesSomeLane(renderLanes, updateLanes)) {\n      didReceiveUpdate = false;\n      // 本`fiber`节点的没有更新, 可以复用, 进入bailout逻辑\n      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);\n    }\n  }\n  // 不能复用, 创建新的fiber节点\n  workInProgress.lanes = NoLanes; // 重置优先级为 NoLanes\n  switch (workInProgress.tag) {\n    case ClassComponent: {\n      const Component = workInProgress.type;\n      const unresolvedProps = workInProgress.pendingProps;\n      const resolvedProps =\n        workInProgress.elementType === Component\n          ? unresolvedProps\n          : resolveDefaultProps(Component, unresolvedProps);\n\n      return updateClassComponent(\n        current,\n        workInProgress,\n        Component,\n        resolvedProps,\n        // 正常情况下渲染优先级会被用于fiber树的构造过程\n        renderLanes,\n      );\n    }\n  }\n}\n```\n\n### 栈帧管理\n\n在`React`源码中, 每一次执行`fiber树`构造(也就是调用`performSyncWorkOnRoot`或者`performConcurrentWorkOnRoot`函数)的过程, 都需要一些全局变量来保存状态. 在上文中已经介绍最核心的全局变量.\n\n如果从单个变量来看, 它们就是一个个的全局变量. 如果将这些全局变量组合起来, 它们代表了当前`fiber树`构造的活动记录. 通过这一组全局变量, 可以还原`fiber树`构造过程(比如时间切片的实现过程(参考[React 调度原理](./scheduler.md#内核)), `fiber树`构造过程被打断之后需要还原进度, 全靠这一组全局变量). 所以每次`fiber树`构造是一个独立的过程, 需要`独立的`一组全局变量, 在`React`内部把这一个独立的过程封装为一个栈帧`stack`(简单来说就是每次构造都需要独立的空间. 对于`栈帧`的深入理解, 请读者自行参考其他资料).\n\n所以在进行`fiber树`构造之前, 如果不需要恢复上一次构造进度, 都会刷新栈帧(源码在[prepareFreshStack 函数](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1301-L1337))\n\n```js\nfunction renderRootConcurrent(root: FiberRoot, lanes: Lanes) {\n  const prevExecutionContext = executionContext;\n  executionContext |= RenderContext;\n  const prevDispatcher = pushDispatcher();\n  // 如果fiberRoot变动, 或者update.lane变动, 都会刷新栈帧, 丢弃上一次渲染进度\n  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {\n    resetRenderTimer();\n    // 刷新栈帧\n    prepareFreshStack(root, lanes);\n    startWorkOnPendingInteractions(root, lanes);\n  }\n}\n\n/**\n刷新栈帧: 重置 FiberRoot上的全局属性 和 `fiber树构造`循环过程中的全局变量\n*/\nfunction prepareFreshStack(root: FiberRoot, lanes: Lanes) {\n  // 重置FiberRoot对象上的属性\n  root.finishedWork = null;\n  root.finishedLanes = NoLanes;\n  const timeoutHandle = root.timeoutHandle;\n  if (timeoutHandle !== noTimeout) {\n    root.timeoutHandle = noTimeout;\n    cancelTimeout(timeoutHandle);\n  }\n  if (workInProgress !== null) {\n    let interruptedWork = workInProgress.return;\n    while (interruptedWork !== null) {\n      unwindInterruptedWork(interruptedWork);\n      interruptedWork = interruptedWork.return;\n    }\n  }\n  // 重置全局变量\n  workInProgressRoot = root;\n  workInProgress = createWorkInProgress(root.current, null); // 给HostRootFiber对象创建一个alternate, 并将其设置成全局 workInProgress\n  workInProgressRootRenderLanes =\n    subtreeRenderLanes =\n    workInProgressRootIncludedLanes =\n      lanes;\n  workInProgressRootExitStatus = RootIncomplete;\n  workInProgressRootFatalError = null;\n  workInProgressRootSkippedLanes = NoLanes;\n  workInProgressRootUpdatedLanes = NoLanes;\n  workInProgressRootPingedLanes = NoLanes;\n}\n```\n\n注意其中的`createWorkInProgress(root.current, null)`, 其参数`root.current`即`HostRootFiber`, 作用是给`HostRootFiber`创建一个`alternate`副本.`workInProgress`指针指向这个副本(即`workInProgress = HostRootFiber.alternate`), 在上文`double buffering`中分析过, `HostRootFiber.alternate`是`正在构造的fiber树`的根节点.\n\n## 总结\n\n本节是`fiber树构造`的准备篇, 首先在宏观上从不同的视角(`任务调度循环`, `fiber树构造循环`)介绍了`fiber树构造`在`React`体系中所处的位置, 然后深入`react-reconciler`包分析`fiber树构造`过程中需要使用到的全局变量, 并解读了`双缓冲技术`和`优先级(车道模型)`的使用, 最后解释`栈帧管理`的实现细节. 有了这些基础知识, `fiber树构造`的具体实现过程会更加简单清晰.\n"
  },
  {
    "path": "docs/main/fibertree-update.md",
    "content": "---\ntitle: fiber 树构造(对比更新)\ngroup: 运行核心\norder: 6\n---\n\n# fiber 树构造(对比更新)\n\n在前文[fiber 树构造(初次创建)](./fibertree-create.md)一文的介绍中, 演示了`fiber树构造循环`中逐步构造`fiber树`的过程. 由于是初次创建, 所以在构造过程中, 所有节点都是新建, 并没有复用旧节点.\n\n本节讨论`对比更新`这种情况(在`Legacy`模式下进行分析). 在阅读本节之前, 最好对[fiber 树构造(初次创建)](./fibertree-create.md)有一些了解, 其中有很多相似逻辑不再重复叙述, 本节重点突出`对比更新`与`初次创建`的不同之处.\n\n本节示例代码如下([codesandbox 地址](https://codesandbox.io/s/angry-williams-l1mze?file=/src/App.js)):\n\n```js\nimport React from 'react';\n\nclass App extends React.Component {\n  state = {\n    list: ['A', 'B', 'C'],\n  };\n  onChange = () => {\n    this.setState({ list: ['C', 'A', 'X'] });\n  };\n  componentDidMount() {\n    console.log(`App Mount`);\n  }\n  render() {\n    return (\n      <>\n        <Header />\n        <button onClick={this.onChange}>change</button>\n        <div className=\"content\">\n          {this.state.list.map((item) => (\n            <p key={item}>{item}</p>\n          ))}\n        </div>\n      </>\n    );\n  }\n}\n\nclass Header extends React.PureComponent {\n  render() {\n    return (\n      <>\n        <h1>title</h1>\n        <h2>title2</h2>\n      </>\n    );\n  }\n}\nexport default App;\n```\n\n在`初次渲染`完成之后, 与`fiber树`相关的内存结构如下(后文以此图为基础, 演示`对比更新`过程):\n\n![](../../snapshots/fibertree-update/beforeupdate.png)\n\n## 更新入口\n\n前文[reconciler 运作流程](./reconciler-workflow.md#输入)中总结的 4 个阶段(从输入到输出), 其中承接输入的函数只有`scheduleUpdateOnFiber`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619)).在`react-reconciler`对外暴露的 api 函数中, 只要涉及到需要改变 fiber 的操作(无论是`首次渲染`或`对比更新`), 最后都会间接调用`scheduleUpdateOnFiber`, `scheduleUpdateOnFiber`函数是输入链路中的`必经之路`.\n\n### 3 种更新方式\n\n如要主动发起更新, 有 3 种常见方式:\n\n1. `Class`组件中调用`setState`.\n2. `Function`组件中调用`hook`对象暴露出的`dispatchAction`.\n3. 在`container`节点上重复调用`render`([官网示例](https://reactjs.org/docs/rendering-elements.html#react-only-updates-whats-necessary))\n\n下面列出这 3 种更新方式的源码:\n\n#### setState\n\n在`Component`对象的原型上挂载有`setState`([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactBaseClasses.js#L57-L66)):\n\n```js\nComponent.prototype.setState = function (partialState, callback) {\n  this.updater.enqueueSetState(this, partialState, callback, 'setState');\n};\n```\n\n在[fiber 树构造(初次创建)](./fibertree-create.md)中的`beginWork`阶段, class 类型的组件初始化完成之后, `this.updater`对象如下([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L193-L225)):\n\n```js\nconst classComponentUpdater = {\n  isMounted,\n  enqueueSetState(inst, payload, callback) {\n    // 1. 获取class实例对应的fiber节点\n    const fiber = getInstance(inst);\n    // 2. 创建update对象\n    const eventTime = requestEventTime();\n    const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级\n    const update = createUpdate(eventTime, lane);\n    update.payload = payload;\n    if (callback !== undefined && callback !== null) {\n      update.callback = callback;\n    }\n    // 3. 将update对象添加到当前Fiber节点的updateQueue队列当中\n    enqueueUpdate(fiber, update);\n    // 4. 进入reconciler运作流程中的`输入`环节\n    scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级\n  },\n};\n```\n\n#### dispatchAction\n\n> 此处只是为了对比`dispatchAction`和`setState`. 有关`hook`原理的深入分析, 在`hook 原理`章节中详细讨论.\n\n在`function类型`组件中, 如果使用`hook(useState)`, 则可以通过`hook api`暴露出的`dispatchAction`([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1645-L1753))来更新\n\n```js\nfunction dispatchAction<S, A>(\n  fiber: Fiber,\n  queue: UpdateQueue<S, A>,\n  action: A,\n) {\n  // 1. 创建update对象\n  const eventTime = requestEventTime();\n  const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级\n  const update: Update<S, A> = {\n    lane,\n    action,\n    eagerReducer: null,\n    eagerState: null,\n    next: (null: any),\n  };\n  // 2. 将update对象添加到当前Hook对象的updateQueue队列当中\n  const pending = queue.pending;\n  if (pending === null) {\n    update.next = update;\n  } else {\n    update.next = pending.next;\n    pending.next = update;\n  }\n  queue.pending = update;\n  // 3. 请求调度, 进入reconciler运作流程中的`输入`环节.\n  scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级\n}\n```\n\n#### 重复调用 render\n\n```js\nimport ReactDOM from 'react-dom';\nfunction tick() {\n  const element = (\n    <div>\n      <h1>Hello, world!</h1>\n      <h2>It is {new Date().toLocaleTimeString()}.</h2>\n    </div>\n  );\n  ReactDOM.render(element, document.getElementById('root'));\n}\nsetInterval(tick, 1000);\n```\n\n对于重复`render`, 在[React 应用的启动过程](./bootstrap.md)中已有说明, 调用路径包含`updateContainer-->scheduleUpdateOnFiber`\n\n> 故无论从哪个入口进行更新, 最终都会进入`scheduleUpdateOnFiber`, 再次证明`scheduleUpdateOnFiber`是`输入`阶段的必经函数(参考[reconciler 运作流程](./reconciler-workflow.md)).\n\n## 构造阶段\n\n逻辑来到[scheduleUpdateOnFiber](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619)函数:\n\n```js\n// ...省略部分代码\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber, // fiber表示被更新的节点\n  lane: Lane, // lane表示update优先级\n  eventTime: number,\n) {\n  const root = markUpdateLaneFromFiberToRoot(fiber, lane);\n  if (lane === SyncLane) {\n    if (\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      // 初次渲染\n      performSyncWorkOnRoot(root);\n    } else {\n      // 对比更新\n      ensureRootIsScheduled(root, eventTime);\n    }\n  }\n  mostRecentlyUpdatedRoot = root;\n}\n```\n\n`对比更新`与`初次渲染`的不同点:\n\n1. [markUpdateLaneFromFiberToRoot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L625-L667)函数, 只在`对比更新`阶段才发挥出它的作用, 它找出了`fiber树`中受到本次`update`影响的所有节点, 并设置这些节点的`fiber.lanes`或`fiber.childLanes`(在`legacy`模式下为`SyncLane`)以备`fiber树构造`阶段使用.\n\n```js\nfunction markUpdateLaneFromFiberToRoot(\n  sourceFiber: Fiber, // sourceFiber表示被更新的节点\n  lane: Lane, // lane表示update优先级\n): FiberRoot | null {\n  // 1. 将update优先级设置到sourceFiber.lanes\n  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);\n  let alternate = sourceFiber.alternate;\n  if (alternate !== null) {\n    // 同时设置sourceFiber.alternate的优先级\n    alternate.lanes = mergeLanes(alternate.lanes, lane);\n  }\n  // 2. 从sourceFiber开始, 向上遍历所有节点, 直到HostRoot. 设置沿途所有节点(包括alternate)的childLanes\n  let node = sourceFiber;\n  let parent = sourceFiber.return;\n  while (parent !== null) {\n    parent.childLanes = mergeLanes(parent.childLanes, lane);\n    alternate = parent.alternate;\n    if (alternate !== null) {\n      alternate.childLanes = mergeLanes(alternate.childLanes, lane);\n    }\n    node = parent;\n    parent = parent.return;\n  }\n  if (node.tag === HostRoot) {\n    const root: FiberRoot = node.stateNode;\n    return root;\n  } else {\n    return null;\n  }\n}\n```\n\n### markUpdateLaneFromFiberToRoot\n\n下图表示了`markUpdateLaneFromFiberToRoot`的具体作用:\n\n- 以`sourceFiber`为起点, 设置起点的`fiber.lanes`\n- 从起点开始, 直到`HostRootFiber`, 设置父路径上所有节点(也包括`fiber.alternate`)的`fiber.childLanes`.\n- 通过设置`fiber.lanes`和`fiber.childLanes`就可以辅助判断子树是否需要更新(在下文`循环构造`中详细说明).\n\n![](../../snapshots/fibertree-update/markupdatelane.png)\n\n2. `对比更新`没有直接调用`performSyncWorkOnRoot`, 而是通过调度中心来处理, 由于本示例是在`Legacy`模式下进行, 最后会同步执行`performSyncWorkOnRoot`.(详细原理可以参考[React 调度原理(scheduler)](./scheduler.md)). 所以其调用链路`performSyncWorkOnRoot--->renderRootSync--->workLoopSync`与`初次构造`中的一致.\n\n在[renderRootSync](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1490-L1553)中:\n\n```js\nfunction renderRootSync(root: FiberRoot, lanes: Lanes) {\n  const prevExecutionContext = executionContext;\n  executionContext |= RenderContext;\n  // 如果fiberRoot变动, 或者update.lane变动, 都会刷新栈帧, 丢弃上一次渲染进度\n  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {\n    // 刷新栈帧, legacy模式下都会进入\n    prepareFreshStack(root, lanes);\n  }\n  do {\n    try {\n      workLoopSync();\n      break;\n    } catch (thrownValue) {\n      handleError(root, thrownValue);\n    }\n  } while (true);\n  executionContext = prevExecutionContext;\n  // 重置全局变量, 表明render结束\n  workInProgressRoot = null;\n  workInProgressRootRenderLanes = NoLanes;\n  return workInProgressRootExitStatus;\n}\n```\n\n进入循环构造(`workLoopSync`)前, 会刷新栈帧(调用`prepareFreshStack`)(参考[fiber 树构造(基础准备)](./fibertree-prepare.md#栈帧管理)中`栈帧管理`).\n\n此时的内存结构如下:\n\n![](../../snapshots/fibertree-update/status-refreshstack.png)\n\n注意:\n\n- `fiberRoot.current`指向与当前页面对应的`fiber树`, `workInProgress`指向正在构造的`fiber树`.\n- 刷新栈帧会调用`createWorkInProgress()`, 使得`workInProgress.flags和workInProgress.effects`都已经被重置. 且`workInProgress.child = current.child`. 所以在进入`循环构造`之前, `HostRootFiber`与`HostRootFiber.alternate`共用一个`child`(这里是`fiber(<App/>)`).\n\n### 循环构造\n\n回顾一下[fiber 树构造(初次创建)](./fibertree-create.md)中的介绍. 整个`fiber树构造`是一个深度优先遍历(可参考[React 算法之深度优先遍历](../algorithm/dfs.md)), 其中有 2 个重要的变量`workInProgress`和`current`(可参考[fiber 树构造(基础准备)](./fibertree-prepare.md#双缓冲技术)中介绍的`双缓冲技术`):\n\n- `workInProgress`和`current`都视为指针\n- `workInProgress`指向当前正在构造的`fiber`节点\n- `current = workInProgress.alternate`(即`fiber.alternate`), 指向当前页面正在使用的`fiber`节点.\n\n在深度优先遍历中, 每个`fiber`节点都会经历 2 个阶段:\n\n1. 探寻阶段 `beginWork`\n2. 回溯阶段 `completeWork`\n\n这 2 个阶段共同完成了每一个`fiber`节点的创建(或更新), 所有`fiber`节点则构成了`fiber树`.\n\n```js\nfunction workLoopSync() {\n  while (workInProgress !== null) {\n    performUnitOfWork(workInProgress);\n  }\n}\n\n// ... 省略部分无关代码\nfunction performUnitOfWork(unitOfWork: Fiber): void {\n  // unitOfWork就是被传入的workInProgress\n  const current = unitOfWork.alternate;\n  let next;\n  next = beginWork(current, unitOfWork, subtreeRenderLanes);\n  unitOfWork.memoizedProps = unitOfWork.pendingProps;\n  if (next === null) {\n    // 如果没有派生出新的节点, 则进入completeWork阶段, 传入的是当前unitOfWork\n    completeUnitOfWork(unitOfWork);\n  } else {\n    workInProgress = next;\n  }\n}\n```\n\n注意: 在`对比更新`过程中`current = unitOfWork.alternate;`不为`null`, 后续的调用逻辑中会大量使用此处传入的`current`.\n\n### 探寻阶段 beginWork\n\n`beginWork(current, unitOfWork, subtreeRenderLanes)`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3083-L3494)).\n\n```js\nfunction beginWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const updateLanes = workInProgress.lanes;\n  if (current !== null) {\n    // 进入对比\n    const oldProps = current.memoizedProps;\n    const newProps = workInProgress.pendingProps;\n    if (\n      oldProps !== newProps ||\n      hasLegacyContextChanged() ||\n      (__DEV__ ? workInProgress.type !== current.type : false)\n    ) {\n      didReceiveUpdate = true;\n    } else if (!includesSomeLane(renderLanes, updateLanes)) {\n      // 当前渲染优先级renderLanes不包括fiber.lanes, 表明当前fiber节点无需更新\n      didReceiveUpdate = false;\n      switch (\n        workInProgress.tag\n        // switch 语句中包括 context相关逻辑, 本节暂不讨论(不影响分析fiber树构造)\n      ) {\n      }\n      // 当前fiber节点无需更新, 调用bailoutOnAlreadyFinishedWork循环检测子节点是否需要更新\n      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);\n    }\n  }\n  // 余下逻辑与初次创建共用\n  // 1. 设置workInProgress优先级为NoLanes(最高优先级)\n  workInProgress.lanes = NoLanes;\n  // 2. 根据workInProgress节点的类型, 用不同的方法派生出子节点\n  switch (\n    workInProgress.tag // 只列出部分case\n  ) {\n    case ClassComponent: {\n      const Component = workInProgress.type;\n      const unresolvedProps = workInProgress.pendingProps;\n      const resolvedProps =\n        workInProgress.elementType === Component\n          ? unresolvedProps\n          : resolveDefaultProps(Component, unresolvedProps);\n      return updateClassComponent(\n        current,\n        workInProgress,\n        Component,\n        resolvedProps,\n        renderLanes,\n      );\n    }\n    case HostRoot:\n      return updateHostRoot(current, workInProgress, renderLanes);\n    case HostComponent:\n      return updateHostComponent(current, workInProgress, renderLanes);\n    case HostText:\n      return updateHostText(current, workInProgress);\n    case Fragment:\n      return updateFragment(current, workInProgress, renderLanes);\n  }\n}\n```\n\n#### `bailout`逻辑 {#bailout}\n\n> `bail out`英文短语翻译为`解救, 纾困`, 在源码中, `bailout`用于判断子树节点是否完全复用, 如果可以复用, 则会略过 fiber 树构造.\n\n与`初次创建`不同, 在`对比更新`过程中, 如果是`老节点`, 那么`current !== null`, 需要进行对比, 然后决定是否复用老节点及其子树(即`bailout`逻辑).\n\n1. `!includesSomeLane(renderLanes, updateLanes)`这个判断分支, 包含了`渲染优先级`和`update优先级`的比较(详情可以回顾[fiber 树构造(基础准备)](./fibertree-prepare.md#优先级)中`优先级`相关解读), 如果当前节点无需更新, 则会进入`bailout`逻辑.\n2. 最后会调用`bailoutOnAlreadyFinishedWork`:\n   - 如果同时满足`!includesSomeLane(renderLanes, workInProgress.childLanes)`, 表明该 fiber 节点及其子树都无需更新, 可直接进入回溯阶段(`completeUnitOfWork`)\n   - 如果不满足`!includesSomeLane(renderLanes, workInProgress.childLanes)`, 意味着子节点需要更新, `clone`并返回子节点.\n\n```js\n// 省略部分无关代码\nfunction bailoutOnAlreadyFinishedWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {\n    // 渲染优先级不包括 workInProgress.childLanes, 表明子节点也无需更新. 返回null, 直接进入回溯阶段.\n    return null;\n  } else {\n    // 本fiber虽然不用更新, 但是子节点需要更新. clone并返回子节点\n    cloneChildFibers(current, workInProgress);\n    return workInProgress.child;\n  }\n}\n```\n\n注意: `cloneChildFibers`内部调用`createWorkInProgress`, 在构造`fiber`节点时会优先复用`workInProgress.alternate`(不开辟新的内存空间), 否则才会创建新的`fiber`对象.\n\n#### `updateXXX`函数\n\n`updateXXX`函数(如: updateHostRoot, updateClassComponent 等)的主干逻辑与`初次构造`过程完全一致, 总的目的是为了向下生成子节点, 并在这个过程中调用`reconcileChildren`调和函数, 只要`fiber`节点有副作用, 就会把特殊操作设置到`fiber.flags`(如:`节点ref`,`class组件的生命周期`,`function组件的hook`,`节点删除`等).\n\n`对比更新`过程的不同之处:\n\n1. `bailoutOnAlreadyFinishedWork`\n   - `对比更新`时如果遇到当前节点无需更新(如: `class`类型的节点且`shouldComponentUpdate`返回`false`), 会再次进入`bailout`逻辑.\n2. `reconcileChildren`调和函数\n   - 调和函数是`updateXXX`函数中的一项重要逻辑, 它的作用是向下生成子节点, 并设置`fiber.flags`.\n   - `初次创建`时`fiber`节点没有比较对象, 所以在向下生成子节点的时候没有任何多余的逻辑, 只管创建就行.\n   - `对比更新`时需要把`ReactElement`对象与`旧fiber`对象进行比较, 来判断是否需要复用`旧fiber`对象.\n\n注: 本节的重点是`fiber树构造`, 在`对比更新`过程中`reconcileChildren()函数`实现的`diff`算法十分重要, 但是它只是处于算法层面, 对于`diff`算法的实现,在[React 算法之调和算法](../algorithm/diff.md)中单独分析.\n\n本节只需要先了解调和函数目的:\n\n1. 给新增,移动,和删除节点设置`fiber.flags`(新增,移动: `Placement`, 删除: `Deletion`)\n2. 如果是需要删除的`fiber`, [除了自身打上`Deletion`之外, 还要将其添加到父节点的`effects`链表中](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.old.js#L275-L294)(正常副作用队列的处理是在`completeWork`函数, 但是该节点(被删除)会脱离`fiber`树, 不会再进入`completeWork`阶段, 所以在`beginWork`阶段提前加入副作用队列).\n\n### 回溯阶段 completeWork\n\n`completeUnitOfWork(unitOfWork)函数`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1670-L1802))在`初次创建`和`对比更新`逻辑一致, 都是处理`beginWork` 阶段已经创建出来的 `fiber` 节点, 最后创建(更新)DOM 对象, 并上移副作用队列.\n\n在这里我们重点关注`completeWork`函数中, `current !== null`的情况:\n\n```js\n// ...省略无关代码\nfunction completeWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const newProps = workInProgress.pendingProps;\n  switch (workInProgress.tag) {\n    case HostComponent: {\n      // 非文本节点\n      popHostContext(workInProgress);\n      const rootContainerInstance = getRootHostContainer();\n      const type = workInProgress.type;\n      if (current !== null && workInProgress.stateNode != null) {\n        // 处理改动\n        updateHostComponent(\n          current,\n          workInProgress,\n          type,\n          newProps,\n          rootContainerInstance,\n        );\n        if (current.ref !== workInProgress.ref) {\n          markRef(workInProgress);\n        }\n      } else {\n        // ...省略无关代码\n      }\n      return null;\n    }\n    case HostText: {\n      // 文本节点\n      const newText = newProps;\n      if (current && workInProgress.stateNode != null) {\n        const oldText = current.memoizedProps;\n        // 处理改动\n        updateHostText(current, workInProgress, oldText, newText);\n      } else {\n        // ...省略无关代码\n      }\n      return null;\n    }\n  }\n}\n```\n\n```js\nupdateHostComponent = function (\n  current: Fiber,\n  workInProgress: Fiber,\n  type: Type,\n  newProps: Props,\n  rootContainerInstance: Container,\n) {\n  const oldProps = current.memoizedProps;\n  if (oldProps === newProps) {\n    return;\n  }\n  const instance: Instance = workInProgress.stateNode;\n  const currentHostContext = getHostContext();\n  const updatePayload = prepareUpdate(\n    instance,\n    type,\n    oldProps,\n    newProps,\n    rootContainerInstance,\n    currentHostContext,\n  );\n  workInProgress.updateQueue = (updatePayload: any);\n  // 如果有属性变动, 设置fiber.flags |= Update, 等待`commit`阶段的处理\n  if (updatePayload) {\n    markUpdate(workInProgress);\n  }\n};\nupdateHostText = function (\n  current: Fiber,\n  workInProgress: Fiber,\n  oldText: string,\n  newText: string,\n) {\n  // 如果有属性变动, 设置fiber.flags |= Update, 等待`commit`阶段的处理\n  if (oldText !== newText) {\n    markUpdate(workInProgress);\n  }\n};\n```\n\n可以看到在更新过程中, 如果 DOM 属性有变化, 不会再次新建 DOM 对象, 而是设置`fiber.flags |= Update`, 等待`commit`阶段处理([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.old.js#L197-L248)).\n\n### 过程图解\n\n针对本节的示例代码, 将整个`fiber`树构造过程表示出来:\n\n构造前:\n\n在上文已经说明, 进入循环构造前会调用`prepareFreshStack`刷新栈帧, 在进入`fiber树构造`循环之前, 保持这这个初始化状态:\n\n![](../../snapshots/fibertree-update/unitofwork0.png)\n\n`performUnitOfWork`第 1 次调用(只执行`beginWork`):\n\n- 执行前: `workInProgress`指向`HostRootFiber.alternate`对象, 此时`current = workInProgress.alternate`指向当前页面对应的`fiber`树.\n- 执行过程:\n  - 因为`current !== null`且当前节点`fiber.lanes`不在`渲染优先级`范围内, 故进入`bailoutOnAlreadyFinishedWork`逻辑\n  - 又因为`fiber.childLanes`处于`渲染优先级`范围内, 证明`child`节点需要更新, 克隆`workInProgress.child`节点.\n  - `clone`之后, `新fiber`节点会丢弃`旧fiber`上的标志位(`flags`)和副作用(`effects`), 其他属性会继续保留.\n- 执行后: 返回被`clone`的下级节点`fiber(<App/>)`, 移动`workInProgress`指向子节点`fiber(<App/>)`\n\n![](../../snapshots/fibertree-update/unitofwork1.png)\n\n`performUnitOfWork`第 2 次调用(只执行`beginWork`):\n\n- 执行前: `workInProgress`指向`fiber(<App/>)`节点, 且`current = workInProgress.alternate`有值\n- 执行过程:\n  - 当前节点`fiber.lanes`处于`渲染优先级`范围内, 会进入`updateClassComponent()`函数\n  - 在`updateClassComponent()`函数中, 调用`reconcileChildren()`生成下级子节点.\n- 执行后: 返回下级节点`fiber(<Header/>)`, 移动`workInProgress`指向子节点`fiber(<Header/>)`\n\n![](../../snapshots/fibertree-update/unitofwork2.png)\n\n`performUnitOfWork`第 3 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行前: `workInProgress`指向`fiber(<Header/>)`, 且`current = workInProgress.alternate`有值\n- `beginWork`执行过程:\n  - 当前节点`fiber.lanes`处于`渲染优先级`范围内, 会进入`updateClassComponent()`函数\n  - 在`updateClassComponent()`函数中, 由于此组件是`PureComponent`, `shouldComponentUpdate`判定为`false`,故进入`bailoutOnAlreadyFinishedWork`逻辑.\n  - 又因为`fiber.childLanes`不在`渲染优先级`范围内, 证明`child`节点也不需要更新\n- `beginWork`执行后: 因为完全满足`bailout`逻辑, 返回`null`. 所以进入`completeUnitOfWork(unitOfWork)`函数, 传入的参数`unitOfWork`实际上就是`workInProgress`(此时指向`fiber(<Header/>)`)\n\n![](../../snapshots/fibertree-update/unitofwork3.0.png)\n\n- `completeUnitOfWork`执行前: `workInProgress`指向`fiber(<Header/>)`\n- `completeUnitOfWork`执行过程: 以`fiber(<Header/>)`为起点, 向上回溯\n\n`completeUnitOfWork`第 1 次循环:\n\n1. 执行`completeWork`函数: `class`类型的组件无需处理.\n2. 上移副作用队列: 由于本节点`fiber(header)`没有副作用(`fiber.flags = 0`), 所以执行之后副作用队列没有实质变化(目前为空).\n3. 向上回溯: 由于还有兄弟节点, 把`workInProgress`指向下一个兄弟节点`fiber(button)`, 退出`completeUnitOfWork`.\n\n![](../../snapshots/fibertree-update/unitofwork3.1.png)\n\n`performUnitOfWork`第 4 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行过程: 调用`updateHostComponent`\n  - 本示例中`button`的子节点是一个[直接文本节点](https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-dom/src/client/ReactDOMHostConfig.js#L350-L361),设置[nextChildren = null](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L1147)(源码注释的解释是不用在开辟内存去创建一个文本节点, 同时还能减少向下遍历).\n  - 由于`nextChildren = null`, 经过`reconcileChildren`阶段处理后, 返回值也是`null`\n- `beginWork`执行后: 由于下级节点为`null`, 所以进入`completeUnitOfWork(unitOfWork)`函数, 传入的参数`unitOfWork`实际上就是`workInProgress`(此时指向`fiber(button)`节点)\n\n- `completeUnitOfWork`执行过程: 以`fiber(button)`为起点, 向上回溯\n\n`completeUnitOfWork`第 1 次循环:\n\n1. 执行`completeWork`函数\n   - 因为`fiber(button).stateNode != null`, 所以无需再次创建 DOM 对象. 只需要进一步调用`updateHostComponent()`记录 DOM 属性改动情况\n   - 在`updateHostComponent()`函数中, 又因为`oldProps === newProps`, 所以无需记录改动情况, 直接返回\n2. 上移副作用队列: 由于本节点`fiber(button)`没有副作用(`fiber.flags = 0`), 所以执行之后副作用队列没有实质变化(目前为空).\n3. 向上回溯: 由于还有兄弟节点, 把`workInProgress`指向下一个兄弟节点`fiber(div)`, 退出`completeUnitOfWork`.\n\n![](../../snapshots/fibertree-update/unitofwork4.png)\n\n`performUnitOfWork`第 5 次调用(执行`beginWork`):\n\n- 执行前: `workInProgress`指向`fiber(div)`节点, 且`current = workInProgress.alternate`有值\n- 执行过程:\n  - 在`updateHostComponent()`函数中, 调用`reconcileChildren()`生成下级子节点.\n  - 需要注意的是, 下级子节点是一个可迭代数组, 会把`fiber.child.sibling`一起构造出来, 同时根据需要设置`fiber.flags`. 在本例中, 下级节点有被删除的情况, 被删除的节点会被添加到父节点的副作用队列中(具体实现方式请参考[React 算法之调和算法](../algorithm/diff.md)).\n- 执行后: 返回下级节点`fiber(p)`, 移动`workInProgress`指向子节点`fiber(p)`\n\n![](../../snapshots/fibertree-update/unitofwork5.png)\n\n`performUnitOfWork`第 6 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行过程: 与第 4 次调用中构建`fiber(button)`的逻辑完全一致, 因为都是直接文本节点, `reconcileChildren()`返回的下级子节点为 null.\n- `beginWork`执行后: 由于下级节点为`null`, 所以进入`completeUnitOfWork(unitOfWork)`函数\n\n- `completeUnitOfWork`执行过程: 以`fiber(p)`为起点, 向上回溯\n\n`completeUnitOfWork`第 1 次循环:\n\n1. 执行`completeWork`函数\n   - 因为`fiber(p).stateNode != null`, 所以无需再次创建 DOM 对象. 在`updateHostComponent()`函数中, 又因为节点属性没有变动, 所以无需打标记\n2. 上移副作用队列: 本节点`fiber(p)`没有副作用(`fiber.flags = 0`).\n3. 向上回溯: 由于还有兄弟节点, 把`workInProgress`指向下一个兄弟节点`fiber(p)`, 退出`completeUnitOfWork`.\n\n![](../../snapshots/fibertree-update/unitofwork6.png)\n\n`performUnitOfWork`第 7 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行过程: 与第 4 次调用中构建`fiber(button)`的逻辑完全一致, 因为都是直接文本节点, `reconcileChildren()`返回的下级子节点为 null.\n- `beginWork`执行后: 由于下级节点为`null`, 所以进入`completeUnitOfWork(unitOfWork)`函数\n\n- `completeUnitOfWork`执行过程: 以`fiber(p)`为起点, 向上回溯\n\n`completeUnitOfWork`第 1 次循环:\n\n1. 执行`completeWork`函数:\n\n   - 因为`fiber(p).stateNode != null`, 所以无需再次创建 DOM 对象. 在`updateHostComponent()`函数中, 又因为节点属性没有变动, 所以无需打标记\n\n2. 上移副作用队列: 本节点`fiber(p)`有副作用(`fiber.flags = Placement`), 需要将其添加到父节点的副作用队列之后.\n3. 向上回溯: 由于还有兄弟节点, 把`workInProgress`指向下一个兄弟节点`fiber(p)`, 退出`completeUnitOfWork`.\n\n![](../../snapshots/fibertree-update/unitofwork7.png)\n\n`performUnitOfWork`第 8 次调用(执行`beginWork`和`completeUnitOfWork`):\n\n- `beginWork`执行过程: 本节点`fiber(p)`是一个新增节点, 其`current === null`, 会进入`updateHostComponent()`函数. 因为是直接文本节点, `reconcileChildren()`返回的下级子节点为 null.\n- `beginWork`执行后: 由于下级节点为`null`, 所以进入`completeUnitOfWork(unitOfWork)`函数\n\n- `completeUnitOfWork`执行过程: 以`fiber(p)`为起点, 向上回溯\n\n`completeUnitOfWork`第 1 次循环:\n\n1. 执行`completeWork`函数: 由于本节点是一个新增节点,且`fiber(p).stateNode === null`, 所以创建`fiber(p)`节点对应的`DOM`实例, 挂载到`fiber.stateNode`之上.\n2. 上移副作用队列: 本节点`fiber(p)`有副作用(`fiber.flags = Placement`), 需要将其添加到父节点的副作用队列之后.\n3. 向上回溯: 由于没有兄弟节点, 把`workInProgress`指针指向父节点`fiber(div)`.\n\n![](../../snapshots/fibertree-update/unitofwork8.png)\n\n`completeUnitOfWork`第 2 次循环:\n\n1. 执行`completeWork`函数: 由于`div`组件没有属性变动, 故`updateHostComponent()`没有设置副作用标记\n2. 上移副作用队列: 本节点`fiber(div)`的副作用队列添加到父节点的副作用队列之后.\n3. 向上回溯: 由于没有兄弟节点, 把`workInProgress`指针指向父节点`fiber(<App/>)`\n\n`completeUnitOfWork`第 3 次循环:\n\n1. 执行`completeWork`函数: class 类型的节点无需处理\n2. 上移副作用队列: 本节点`fiber(<App/>)`的副作用队列添加到父节点的副作用队列之后.\n3. 向上回溯: 由于没有兄弟节点, 把`workInProgress`指针指向父节点`fiber(HostRootFiber)`\n\n`completeUnitOfWork`第 4 次循环:\n\n1. 执行`completeWork`函数: `HostRoot`类型的节点无需处理\n2. 向上回溯: 由于父节点为空, 无需进入处理副作用队列的逻辑. 最后设置`workInProgress=null`, 并退出`completeUnitOfWork`\n3. 重置`fiber.childLanes`\n\n到此整个`fiber树构造循环(对比更新)`已经执行完毕, 拥有一棵新的`fiber树`, 并且在`fiber树`的根节点上挂载了副作用队列. `renderRootSync`函数退出之前, 会重置`workInProgressRoot = null`, 表明没有正在进行中的`render`. 且把最新的`fiber树`挂载到`fiberRoot.finishedWork`上. 这时整个 fiber 树的内存结构如下(注意`fiberRoot.finishedWork`和`fiberRoot.current`指针,在`commitRoot`阶段会进行处理):\n\n![](../../snapshots/fibertree-update/fibertree-beforecommit.png)\n\n无论是`初次构造`或者是`对比更新`, 当`fiber树构造`完成之后, 余下的逻辑几乎一致, 在[fiber 树渲染](./fibertree-commit.md)中继续讨论.\n\n## 总结\n\n本节演示了更新阶段`fiber树构造(对比更新)`的全部过程, 跟踪了创建过程中内存引用的变化情况. 与`初次构造`最大的不同在于`fiber节点`是否可以复用, 其中`bailout`逻辑是`fiber子树`能否复用的判断依据.\n"
  },
  {
    "path": "docs/main/hook-effect.md",
    "content": "---\ntitle: Hook 原理(副作用Hook)\ngroup: 状态管理\norder: 3\n---\n\n# Hook 原理(副作用 Hook)\n\n本节建立在前文[Hook 原理(概览)](./hook-summary.md)和[Hook 原理(状态 Hook)](./hook-state.md)的基础之上, 重点讨论`useEffect, useLayoutEffect`等标准的`副作用Hook`.\n\n## 创建 Hook\n\n在`fiber`初次构造阶段, `useEffect`对应源码[mountEffect](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1232-L1248), `useLayoutEffect`对应源码[mountLayoutEffect](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1268-L1273)\n\n`mountEffect`:\n\n```js\nfunction mountEffect(\n  create: () => (() => void) | void,\n  deps: Array<mixed> | void | null,\n): void {\n  return mountEffectImpl(\n    UpdateEffect | PassiveEffect, // fiberFlags\n    HookPassive, // hookFlags\n    create,\n    deps,\n  );\n}\n```\n\n`mountLayoutEffect`:\n\n```js\nfunction mountLayoutEffect(\n  create: () => (() => void) | void,\n  deps: Array<mixed> | void | null,\n): void {\n  return mountEffectImpl(\n    UpdateEffect, // fiberFlags\n    HookLayout, // hookFlags\n    create,\n    deps,\n  );\n}\n```\n\n可见`mountEffect`和`mountLayoutEffect`内部都直接调用[mountEffectImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1193-L1203), 只是参数不同.\n\n`mountEffectImpl`:\n\n```js\nfunction mountEffectImpl(fiberFlags, hookFlags, create, deps): void {\n  // 1. 创建hook\n  const hook = mountWorkInProgressHook();\n  const nextDeps = deps === undefined ? null : deps;\n  // 2. 设置workInProgress的副作用标记\n  currentlyRenderingFiber.flags |= fiberFlags; // fiberFlags 被标记到workInProgress\n  // 2. 创建Effect, 挂载到hook.memoizedState上\n  hook.memoizedState = pushEffect(\n    HookHasEffect | hookFlags, // hookFlags用于创建effect\n    create,\n    undefined,\n    nextDeps,\n  );\n}\n```\n\n`mountEffectImpl`逻辑:\n\n1. 创建`hook`\n2. 设置`workInProgress`的副作用标记: `flags |= fiberFlags`\n3. 创建`effect`(在`pushEffect`中), 挂载到`hook.memoizedState`上, 即 `hook.memoizedState = effect`\n   - 注意: `状态Hook`中`hook.memoizedState = state`\n\n### 创建 Effect\n\n[pushEffect](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1150-L1176):\n\n```js\nfunction pushEffect(tag, create, destroy, deps) {\n  // 1. 创建effect对象\n  const effect: Effect = {\n    tag,\n    create,\n    destroy,\n    deps,\n    next: (null: any),\n  };\n  // 2. 把effect对象添加到环形链表末尾\n  let componentUpdateQueue: null | FunctionComponentUpdateQueue =\n    (currentlyRenderingFiber.updateQueue: any);\n  if (componentUpdateQueue === null) {\n    // 新建 workInProgress.updateQueue 用于挂载effect对象\n    componentUpdateQueue = createFunctionComponentUpdateQueue();\n    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);\n    // updateQueue.lastEffect是一个环形链表\n    componentUpdateQueue.lastEffect = effect.next = effect;\n  } else {\n    const lastEffect = componentUpdateQueue.lastEffect;\n    if (lastEffect === null) {\n      componentUpdateQueue.lastEffect = effect.next = effect;\n    } else {\n      const firstEffect = lastEffect.next;\n      lastEffect.next = effect;\n      effect.next = firstEffect;\n      componentUpdateQueue.lastEffect = effect;\n    }\n  }\n  // 3. 返回effect\n  return effect;\n}\n```\n\n`pushEffect`逻辑:\n\n1. 创建`effect`.\n2. 把`effect`对象添加到环形链表末尾.\n3. 返回`effect`.\n\n`effect`的数据结构:\n\n```js\nexport type Effect = {|\n  tag: HookFlags,\n  create: () => (() => void) | void,\n  destroy: (() => void) | void,\n  deps: Array<mixed> | null,\n  next: Effect,\n|};\n```\n\n- `effect.tag`: 使用位掩码形式, 代表`effect`的类型([源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactHookEffectTags.js#L12-L19)).\n\n  ```js\n  export const NoFlags = /*  */ 0b000;\n  export const HasEffect = /* */ 0b001; // 有副作用, 可以被触发\n  export const Layout = /*    */ 0b010; // Layout, dom突变后同步触发\n  export const Passive = /*   */ 0b100; // Passive, dom突变前异步触发\n  ```\n\n- `effect.create`: 实际上就是通过`useEffect()`所传入的函数.\n- `effect.deps`: 依赖项, 如果依赖项变动, 会创建新的`effect`.\n\n`renderWithHooks`执行完成后, 我们可以画出`fiber`,`hook`,`effect`三者的引用关系:\n\n![](../../snapshots/hook-effect/renderwithhooks-create.png)\n\n现在`workInProgress.flags`被打上了标记, 最后会在`fiber树渲染`阶段的`commitRoot`函数中处理. (这期间的所有过程可以回顾前文`fiber树构造/fiber树渲染`系列, 此处不再赘述)\n\n### useEffect & useLayoutEffect\n\n站在`fiber,hook,effect`的视角, 无需关心这个`hook`是通过`useEffect`还是`useLayoutEffect`创建的. 只需要关心内部`fiber.flags`,`effect.tag`的状态.\n\n所以`useEffect`与`useLayoutEffect`的区别如下:\n\n1. `fiber.flags`不同\n\n- 使用`useEffect`时: `fiber.flags = UpdateEffect | PassiveEffect`.\n- 使用`useLayoutEffect`时: `fiber.flags = UpdateEffect`.\n\n2. `effect.tag`不同\n\n- 使用`useEffect`时: `effect.tag = HookHasEffect | HookPassive`.\n- 使用`useLayoutEffect`时: `effect.tag = HookHasEffect | HookLayout`.\n\n## 处理 Effect 回调\n\n完成`fiber树构造`后, 逻辑会进入`渲染`阶段. 通过[fiber 树渲染](./fibertree-commit.md)中的介绍, 在`commitRootImpl`函数中, 整个渲染过程被 3 个函数分布实现:\n\n1. [commitBeforeMutationEffects](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L2256-L2300)\n2. [commitMutationEffects](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L2302-L2383)\n3. [commitLayoutEffects](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L2385-L2432)\n\n这 3 个函数会处理`fiber.flags`, 也会根据情况处理`fiber.updateQueue.lastEffect`\n\n### commitBeforeMutationEffects\n\n第一阶段: dom 变更之前, 处理副作用队列中带有`Passive`标记的`fiber`节点.\n\n```js\nfunction commitBeforeMutationEffects() {\n  while (nextEffect !== null) {\n    // ...省略无关代码, 只保留Hook相关\n\n    // 处理`Passive`标记\n    const flags = nextEffect.flags;\n    if ((flags & Passive) !== NoFlags) {\n      if (!rootDoesHavePassiveEffects) {\n        rootDoesHavePassiveEffects = true;\n        scheduleCallback(NormalSchedulerPriority, () => {\n          flushPassiveEffects();\n          return null;\n        });\n      }\n    }\n    nextEffect = nextEffect.nextEffect;\n  }\n}\n```\n\n注意: 由于`flushPassiveEffects`被包裹在`scheduleCallback`回调中, 由`调度中心`来处理, 且参数是`NormalSchedulerPriority`, 故这是一个异步回调(具体原理可以回顾[React 调度原理(scheduler)](./scheduler.md)).\n\n由于`scheduleCallback(NormalSchedulerPriority,callback)`是异步的, `flushPassiveEffects`并不会立即执行. 此处先跳过`flushPassiveEffects`的分析, 继续跟进`commitRoot`.\n\n### commitMutationEffects\n\n第二阶段: dom 变更, 界面得到更新.\n\n```js\nfunction commitMutationEffects(\n  root: FiberRoot,\n  renderPriorityLevel: ReactPriorityLevel,\n) {\n  // ...省略无关代码, 只保留Hook相关\n  while (nextEffect !== null) {\n    const flags = nextEffect.flags;\n    const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);\n    switch (primaryFlags) {\n      case Update: {\n        // useEffect,useLayoutEffect都会设置Update标记\n        // 更新节点\n        const current = nextEffect.alternate;\n        commitWork(current, nextEffect);\n        break;\n      }\n    }\n    nextEffect = nextEffect.nextEffect;\n  }\n}\n\nfunction commitWork(current: Fiber | null, finishedWork: Fiber): void {\n  // ...省略无关代码, 只保留Hook相关\n  switch (finishedWork.tag) {\n    case FunctionComponent:\n    case ForwardRef:\n    case MemoComponent:\n    case SimpleMemoComponent:\n    case Block: {\n      // 在突变阶段调用销毁函数, 保证所有的effect.destroy函数都会在effect.create之前执行\n      commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);\n      return;\n    }\n  }\n}\n\n// 依次执行: effect.destroy\nfunction commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {\n  const updateQueue: FunctionComponentUpdateQueue | null =\n    (finishedWork.updateQueue: any);\n  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;\n  if (lastEffect !== null) {\n    const firstEffect = lastEffect.next;\n    let effect = firstEffect;\n    do {\n      if ((effect.tag & tag) === tag) {\n        // 根据传入的tag过滤 effect链表.\n        const destroy = effect.destroy;\n        effect.destroy = undefined;\n        if (destroy !== undefined) {\n          destroy();\n        }\n      }\n      effect = effect.next;\n    } while (effect !== firstEffect);\n  }\n}\n```\n\n调用关系: `commitMutationEffects->commitWork->commitHookEffectListUnmount`.\n\n- 注意在调用`commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork)`时, 参数是`HookLayout | HookHasEffect`.\n- 而`HookLayout | HookHasEffect`是通过`useLayoutEffect`创建的`effect`. 所以`commitHookEffectListUnmount`函数只能处理由`useLayoutEffect()`创建的`effect`.\n- 同步调用`effect.destroy()`.\n\n### commitLayoutEffects\n\n第三阶段: dom 变更后\n\n```js\nfunction commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {\n  // ...省略无关代码, 只保留Hook相关\n  while (nextEffect !== null) {\n    const flags = nextEffect.flags;\n    if (flags & (Update | Callback)) {\n      // useEffect,useLayoutEffect都会设置Update标记\n      const current = nextEffect.alternate;\n      commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);\n    }\n    nextEffect = nextEffect.nextEffect;\n  }\n}\n\nfunction commitLifeCycles(\n  finishedRoot: FiberRoot,\n  current: Fiber | null,\n  finishedWork: Fiber,\n  committedLanes: Lanes,\n): void {\n  // ...省略无关代码, 只保留Hook相关\n  switch (finishedWork.tag) {\n    case FunctionComponent:\n    case ForwardRef:\n    case SimpleMemoComponent:\n    case Block: {\n      // 在此之前commitMutationEffects函数中, effect.destroy已经被调用, 所以effect.destroy永远不会影响到effect.create\n      commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);\n\n      schedulePassiveEffects(finishedWork);\n      return;\n    }\n  }\n}\n\nfunction commitHookEffectListMount(tag: number, finishedWork: Fiber) {\n  const updateQueue: FunctionComponentUpdateQueue | null =\n    (finishedWork.updateQueue: any);\n  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;\n  if (lastEffect !== null) {\n    const firstEffect = lastEffect.next;\n    let effect = firstEffect;\n    do {\n      if ((effect.tag & tag) === tag) {\n        const create = effect.create;\n        effect.destroy = create();\n      }\n      effect = effect.next;\n    } while (effect !== firstEffect);\n  }\n}\n```\n\n1. 调用关系: `commitLayoutEffects->commitLayoutEffectOnFiber(commitLifeCycles)->commitHookEffectListMount`.\n\n   - 注意在调用`commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork)`时, 参数是`HookLayout | HookHasEffect`,所以只处理由`useLayoutEffect()`创建的`effect`.\n   - 调用`effect.create()`之后, 将返回值赋值到`effect.destroy`.\n\n2. 为`flushPassiveEffects`做准备\n\n   - `commitLifeCycles`中的`schedulePassiveEffects(finishedWork)`, 其形参`finishedWork`实际上指代当前正在被遍历的`有副作用的fiber`\n   - `schedulePassiveEffects`比较简单, 就是把带有`Passive`标记的`effect`筛选出来(由`useEffect`创建), 添加到一个全局数组(`pendingPassiveHookEffectsUnmount`和`pendingPassiveHookEffectsMount`).\n\n     ```js\n     function schedulePassiveEffects(finishedWork: Fiber) {\n       // 1. 获取 fiber.updateQueue\n       const updateQueue: FunctionComponentUpdateQueue | null =\n         (finishedWork.updateQueue: any);\n       // 2. 获取 effect环形队列\n       const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;\n       if (lastEffect !== null) {\n         const firstEffect = lastEffect.next;\n         let effect = firstEffect;\n         do {\n           const { next, tag } = effect;\n           // 3. 筛选出由useEffect()创建的`effect`\n           if (\n             (tag & HookPassive) !== NoHookEffect &&\n             (tag & HookHasEffect) !== NoHookEffect\n           ) {\n             // 把effect添加到全局数组, 等待`flushPassiveEffects`处理\n             enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);\n             enqueuePendingPassiveHookEffectMount(finishedWork, effect);\n           }\n           effect = next;\n         } while (effect !== firstEffect);\n       }\n     }\n\n     export function enqueuePendingPassiveHookEffectUnmount(\n       fiber: Fiber,\n       effect: HookEffect,\n     ): void {\n       // unmount effects 数组\n       pendingPassiveHookEffectsUnmount.push(effect, fiber);\n     }\n\n     export function enqueuePendingPassiveHookEffectMount(\n       fiber: Fiber,\n       effect: HookEffect,\n     ): void {\n       // mount effects 数组\n       pendingPassiveHookEffectsMount.push(effect, fiber);\n     }\n     ```\n\n综上`commitMutationEffects`和`commitLayoutEffects`2 个函数, 带有`Layout`标记的`effect`(由`useLayoutEffect`创建), 已经得到了完整的回调处理(`destroy`和`create`已经被调用).\n\n如下图:\n其中第一个`effect`拥有`Layout`标记,会执行`effect.destroy(); effect.destroy = effect.create()`\n\n![](../../snapshots/hook-effect/hook-commit-layout.png)\n\n### flushPassiveEffects\n\n在上文`commitBeforeMutationEffects`阶段, 异步调用了`flushPassiveEffects`. 在这期间带有`Passive`标记的`effect`已经被添加到`pendingPassiveHookEffectsUnmount`和`pendingPassiveHookEffectsMount`全局数组中.\n\n接下来`flushPassiveEffects`就可以脱离`fiber节点`, 直接访问`effects`\n\n```js\nexport function flushPassiveEffects(): boolean {\n  // Returns whether passive effects were flushed.\n  if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {\n    const priorityLevel =\n      pendingPassiveEffectsRenderPriority > NormalSchedulerPriority\n        ? NormalSchedulerPriority\n        : pendingPassiveEffectsRenderPriority;\n    pendingPassiveEffectsRenderPriority = NoSchedulerPriority;\n    // `runWithPriority`设置Schedule中的调度优先级, 如果在flushPassiveEffectsImpl中处理effect时又发起了新的更新, 那么新的update.lane将会受到这个priorityLevel影响.\n    return runWithPriority(priorityLevel, flushPassiveEffectsImpl);\n  }\n  return false;\n}\n\n// ...省略无关代码, 只保留Hook相关\nfunction flushPassiveEffectsImpl() {\n  if (rootWithPendingPassiveEffects === null) {\n    return false;\n  }\n  rootWithPendingPassiveEffects = null;\n  pendingPassiveEffectsLanes = NoLanes;\n\n  // 1. 执行 effect.destroy()\n  const unmountEffects = pendingPassiveHookEffectsUnmount;\n  pendingPassiveHookEffectsUnmount = [];\n  for (let i = 0; i < unmountEffects.length; i += 2) {\n    const effect = ((unmountEffects[i]: any): HookEffect);\n    const fiber = ((unmountEffects[i + 1]: any): Fiber);\n    const destroy = effect.destroy;\n    effect.destroy = undefined;\n    if (typeof destroy === 'function') {\n      destroy();\n    }\n  }\n\n  // 2. 执行新 effect.create(), 重新赋值到 effect.destroy\n  const mountEffects = pendingPassiveHookEffectsMount;\n  pendingPassiveHookEffectsMount = [];\n  for (let i = 0; i < mountEffects.length; i += 2) {\n    const effect = ((mountEffects[i]: any): HookEffect);\n    const fiber = ((mountEffects[i + 1]: any): Fiber);\n    effect.destroy = create();\n  }\n}\n```\n\n其核心逻辑:\n\n1. 遍历`pendingPassiveHookEffectsUnmount`中的所有`effect`, 调用`effect.destroy()`.\n   - 同时清空`pendingPassiveHookEffectsUnmount`\n2. 遍历`pendingPassiveHookEffectsMount`中的所有`effect`, 调用`effect.create()`, 并更新`effect.destroy`.\n   - 同时清空`pendingPassiveHookEffectsMount`\n\n所以, 带有`Passive`标记的`effect`, 在`flushPassiveEffects`函数中得到了完整的回调处理.\n\n如下图:\n其中拥有`Passive`标记的`effect`, 都会执行`effect.destroy(); effect.destroy = effect.create()`\n\n![](../../snapshots/hook-effect/hook-flushpassive.png)\n\n## 更新 Hook\n\n假设在初次调用之后, 发起更新, 会再次执行`function`, 这时`function`中使用的`useEffect`, `useLayoutEffect`等`api`也会再次执行.\n\n在更新过程中`useEffect`对应源码[updateEffect](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1250-L1266), `useLayoutEffect`对应源码[updateLayoutEffect](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1275-L1280).它们内部都会调用`updateEffectImpl`, 与初次创建时一样, 只是参数不同.\n\n### 更新 Effect\n\n[updateEffectImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1205-L1230):\n\n```js\nfunction updateEffectImpl(fiberFlags, hookFlags, create, deps): void {\n  // 1. 获取当前hook\n  const hook = updateWorkInProgressHook();\n  const nextDeps = deps === undefined ? null : deps;\n  let destroy = undefined;\n  // 2. 分析依赖\n  if (currentHook !== null) {\n    const prevEffect = currentHook.memoizedState;\n    // 继续使用先前effect.destroy\n    destroy = prevEffect.destroy;\n    if (nextDeps !== null) {\n      const prevDeps = prevEffect.deps;\n      // 比较依赖是否变化\n      if (areHookInputsEqual(nextDeps, prevDeps)) {\n        // 2.1 如果依赖不变, 新建effect(tag不含HookHasEffect)\n        pushEffect(hookFlags, create, destroy, nextDeps);\n        return;\n      }\n    }\n  }\n  // 2.2 如果依赖改变, 更改fiber.flag, 新建effect\n  currentlyRenderingFiber.flags |= fiberFlags;\n\n  hook.memoizedState = pushEffect(\n    HookHasEffect | hookFlags,\n    create,\n    destroy,\n    nextDeps,\n  );\n}\n```\n\n[updateEffectImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1205-L1230)与[mountEffectImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1193-L1203)逻辑有所不同: - 如果`useEffect/useLayoutEffect`的依赖不变, 新建的`effect`对象不带`HasEffect`标记.\n\n注意: 无论依赖是否变化, 都复用之前的`effect.destroy`. 等待`commitRoot`阶段的调用(上文已经说明).\n\n如下图:\n\n- 图中第 1,2 个`hook`其`deps`没变, 故`effect.tag`中不会包含`HookHasEffect`.\n- 图中第 3 个`hook`其`deps`改变, 故`effect.tag`中继续含有`HookHasEffect`.\n\n![](../../snapshots/hook-effect/renderwithhooks-update.png)\n\n### 处理 Effect 回调\n\n新的`hook`以及新的`effect`创建完成之后, 余下逻辑与初次渲染完全一致. 处理 Effect 回调时也会根据`effect.tag`进行判断: 只有`effect.tag`包含`HookHasEffect`时才会调用`effect.destroy`和`effect.create()`\n\n## 组件销毁\n\n当`function`组件被销毁时, `fiber`节点必然会被打上`Deletion`标记, 即`fiber.flags |= Deletion`. 带有`Deletion`标记的`fiber`在[commitMutationEffects](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L2302-L2383)被处理:\n\n```js\n// ...省略无关代码\nfunction commitMutationEffects(\n  root: FiberRoot,\n  renderPriorityLevel: ReactPriorityLevel,\n) {\n  while (nextEffect !== null) {\n    const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);\n    switch (primaryFlags) {\n      case Deletion: {\n        commitDeletion(root, nextEffect, renderPriorityLevel);\n        break;\n      }\n    }\n  }\n}\n```\n\n在`commitDeletion`函数之后, 继续调用`unmountHostComponents->commitUnmount`, 在[commitUnmount](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCommitWork.old.js#L866-L963)中, 执行`effect.destroy()`, 结束整个闭环.\n\n## 总结\n\n本节分析了`副作用Hook`从创建到销毁的全部过程, 在`react`内部, 依靠`fiber.flags`和`effect.tag`实现了对`effect`的精准识别. 在`commitRoot`阶段, 对不同类型的`effect`进行处理, 先后调用`effect.destroy()`和`effect.create()`.\n"
  },
  {
    "path": "docs/main/hook-state.md",
    "content": "---\ntitle: Hook 原理(状态Hook)\ngroup: 状态管理\norder: 2\n---\n\n# Hook 原理(状态 Hook)\n\n首先回顾一下前文[Hook 原理(概览)](./hook-summary.md), 其主要内容有:\n\n1. `function`类型的`fiber`节点, 它的处理函数是[updateFunctionComponent](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L702-L783), 其中再通过[renderWithHooks](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L342-L476)调用`function`.\n2. 在`function`中, 通过`Hook Api`(如: `useState, useEffect`)创建`Hook`对象.\n   - `状态Hook`实现了状态持久化(等同于`class组件`维护`fiber.memoizedState`).\n   - `副作用Hook`则实现了维护`fiber.flags`,并提供`副作用回调`(类似于`class组件`的生命周期回调)\n3. 多个`Hook`对象构成一个`链表结构`, 并挂载到`fiber.memoizedState`之上.\n4. `fiber树`更新阶段, 把`current.memoizedState`链表上的所有`Hook`按照顺序克隆到`workInProgress.memoizedState`上, 实现数据的持久化.\n\n在此基础之上, 本节将深入分析`状态Hook`的特性和实现原理.\n\n## 创建 Hook\n\n在`fiber`初次构造阶段, [useState](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1787)对应源码[mountState](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1113-L1136), [useReducer](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1785)对应源码[mountReducer](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L624-L649)\n\n`mountState`:\n\n```js\nfunction mountState<S>(\n  initialState: (() => S) | S,\n): [S, Dispatch<BasicStateAction<S>>] {\n  // 1. 创建hook\n  const hook = mountWorkInProgressHook();\n  if (typeof initialState === 'function') {\n    initialState = initialState();\n  }\n  // 2. 初始化hook的属性\n  // 2.1 设置 hook.memoizedState/hook.baseState\n  // 2.2 设置 hook.queue\n  hook.memoizedState = hook.baseState = initialState;\n  const queue = (hook.queue = {\n    pending: null,\n    dispatch: null,\n    // queue.lastRenderedReducer是内置函数\n    lastRenderedReducer: basicStateReducer,\n    lastRenderedState: (initialState: any),\n  });\n  // 2.3 设置 hook.dispatch\n  const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispatch =\n    (dispatchAction.bind(null, currentlyRenderingFiber, queue): any));\n\n  // 3. 返回[当前状态, dispatch函数]\n  return [hook.memoizedState, dispatch];\n}\n```\n\n`mountReducer`:\n\n```js\nfunction mountReducer<S, I, A>(\n  reducer: (S, A) => S,\n  initialArg: I,\n  init?: (I) => S,\n): [S, Dispatch<A>] {\n  // 1. 创建hook\n  const hook = mountWorkInProgressHook();\n  let initialState;\n  if (init !== undefined) {\n    initialState = init(initialArg);\n  } else {\n    initialState = ((initialArg: any): S);\n  }\n  // 2. 初始化hook的属性\n  // 2.1 设置 hook.memoizedState/hook.baseState\n  hook.memoizedState = hook.baseState = initialState;\n  // 2.2 设置 hook.queue\n  const queue = (hook.queue = {\n    pending: null,\n    dispatch: null,\n    // queue.lastRenderedReducer是由外传入\n    lastRenderedReducer: reducer,\n    lastRenderedState: (initialState: any),\n  });\n  // 2.3 设置 hook.dispatch\n  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(\n    null,\n    currentlyRenderingFiber,\n    queue,\n  ): any));\n\n  // 3. 返回[当前状态, dispatch函数]\n  return [hook.memoizedState, dispatch];\n}\n```\n\n`mountState`和`mountReducer`逻辑简单: 主要负责创建`hook`, 初始化`hook`的属性, 最后返回`[当前状态, dispatch函数]`.\n\n唯一的不同点是`hook.queue.lastRenderedReducer`:\n\n- `mountState`使用的是内置的[basicStateReducer](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L619-L622)\n\n  ```js\n  function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {\n    return typeof action === 'function' ? action(state) : action;\n  }\n  ```\n\n- `mountReducer`使用的是外部传入自定义`reducer`\n\n可见`mountState`是`mountReducer`的一种特殊情况, 即`useState`也是`useReducer`的一种特殊情况, 也是最简单的情况.\n\n`useState`可以转换成`useReducer`:\n\n```js\nconst [state, dispatch] = useState({ count: 0 });\n\n// 等价于\nconst [state, dispatch] = useReducer(\n  function basicStateReducer(state, action) {\n    return typeof action === 'function' ? action(state) : action;\n  },\n  { count: 0 },\n);\n\n// 当需要更新state时, 有2种方式\ndispatch({ count: 1 }); // 1.直接设置\ndispatch((state) => ({ count: state.count + 1 })); // 2.通过回调函数设置\n```\n\n`useReducer`的[官网示例](https://zh-hans.reactjs.org/docs/hooks-reference.html#usereducer):\n\n```js\nconst [state, dispatch] = useReducer(\n  function reducer(state, action) {\n    switch (action.type) {\n      case 'increment':\n        return { count: state.count + 1 };\n      case 'decrement':\n        return { count: state.count - 1 };\n      default:\n        throw new Error();\n    }\n  },\n  { count: 0 },\n);\n\n// 当需要更新state时, 只有1种方式\ndispatch({ type: 'decrement' });\n```\n\n可见, `useState`就是对`useReducer`的基本封装, 内置了一个特殊的`reducer`(后文不再区分`useState, useReducer`, 都以`useState`为例).`创建hook`之后返回值`[hook.memoizedState, dispatch]`中的`dispatch`实际上会调用`reducer`函数.\n\n## 状态初始化\n\n在`useState(initialState)`函数内部, 设置`hook.memoizedState = hook.baseState = initialState;`, 初始状态被同时保存到了`hook.baseState`,`hook.memoizedState`中.\n\n1. `hook.memoizedState`: 当前状态\n2. `hook.baseState`: `基础`状态, 作为合并`hook.baseQueue`的初始值(下文介绍).\n\n最后返回`[hook.memoizedState, dispatch]`, 所以在`function`中使用的是`hook.memoizedState`.\n\n## 状态更新\n\n有如下代码:[![Edit hook-status](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/hook-status-vhlf8?fontsize=14&hidenavigation=1&theme=dark)\n\n```jsx\nimport React, { useState } from 'react';\nexport default function App() {\n  const [count, dispatch] = useState(0);\n  return (\n    <button\n      onClick={() => {\n        dispatch(1);\n        dispatch(3);\n        dispatch(2);\n      }}\n    >\n      {count}\n    </button>\n  );\n}\n```\n\n初次渲染时`count = 0`, 这时`hook`对象的内存状态如下:\n\n![](../../snapshots/hook-state/initial-state.png)\n\n点击`button`, 通过`dispatch`函数进行更新, `dispatch`实际就是[dispatchAction](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1645-L1753):\n\n```js\nfunction dispatchAction<S, A>(\n  fiber: Fiber,\n  queue: UpdateQueue<S, A>,\n  action: A,\n) {\n  // 1. 创建update对象\n  const eventTime = requestEventTime();\n  const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane\n  const update: Update<S, A> = {\n    lane,\n    action,\n    eagerReducer: null,\n    eagerState: null,\n    next: (null: any),\n  };\n\n  // 2. 将update对象添加到hook.queue.pending队列\n  const pending = queue.pending;\n  if (pending === null) {\n    // 首个update, 创建一个环形链表\n    update.next = update;\n  } else {\n    update.next = pending.next;\n    pending.next = update;\n  }\n  queue.pending = update;\n\n  const alternate = fiber.alternate;\n  if (\n    fiber === currentlyRenderingFiber ||\n    (alternate !== null && alternate === currentlyRenderingFiber)\n  ) {\n    // 渲染时更新, 做好全局标记\n    didScheduleRenderPhaseUpdateDuringThisPass =\n      didScheduleRenderPhaseUpdate = true;\n  } else {\n    // ...省略性能优化部分, 下文介绍\n\n    // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.\n    scheduleUpdateOnFiber(fiber, lane, eventTime);\n  }\n}\n```\n\n逻辑十分清晰:\n\n1. 创建`update`对象, 其中`update.lane`代表优先级(可回顾[fiber 树构造(基础准备)](./fibertree-prepare.md#update-lane)中的`update优先级`).\n2. 将`update`对象添加到`hook.queue.pending`环形链表.\n   - `环形链表`的特征: 为了方便添加新元素和快速拿到队首元素(都是`O(1)`), 所以`pending`指针指向了链表中最后一个元素.\n   - 链表的使用方式可以参考[React 算法之链表操作](../algorithm/linkedlist.md)\n3. 发起调度更新: 调用`scheduleUpdateOnFiber`, 进入`reconciler 运作流程`中的输入阶段.\n\n从调用`scheduleUpdateOnFiber`开始, 进入了`react-reconciler`包, 其中的所有逻辑可回顾[reconciler 运作流程](./reconciler-workflow.md), 本节只讨论`状态Hook`相关逻辑.\n\n注意: 本示例中虽然同时执行了 3 次 dispatch, 会请求 3 次调度, 由于调度中心的[节流优化](./scheduler.md##throttle-debounce), 最后只会执行一次渲染\n\n在`fiber树构造(对比更新)`过程中, 再次调用`function`, 这时[useState](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1808)对应的函数是[updateState](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1138-L1142)\n\n```js\nfunction updateState<S>(\n  initialState: (() => S) | S,\n): [S, Dispatch<BasicStateAction<S>>] {\n  return updateReducer(basicStateReducer, (initialState: any));\n}\n```\n\n实际调用[updateReducer](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L651-L783).\n\n在执行`updateReducer`之前, `hook`相关的内存结构如下:\n\n![](../../snapshots/hook-state/before-basequeue-combine.png)\n\n```js\nfunction updateReducer<S, I, A>(\n  reducer: (S, A) => S,\n  initialArg: I,\n  init?: (I) => S,\n): [S, Dispatch<A>] {\n  // 1. 获取workInProgressHook对象\n  const hook = updateWorkInProgressHook();\n  const queue = hook.queue;\n  queue.lastRenderedReducer = reducer;\n  const current: Hook = (currentHook: any);\n  let baseQueue = current.baseQueue;\n\n  // 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue\n  const pendingQueue = queue.pending;\n  if (pendingQueue !== null) {\n    if (baseQueue !== null) {\n      const baseFirst = baseQueue.next;\n      const pendingFirst = pendingQueue.next;\n      baseQueue.next = pendingFirst;\n      pendingQueue.next = baseFirst;\n    }\n    current.baseQueue = baseQueue = pendingQueue;\n    queue.pending = null;\n  }\n  // 3. 状态计算\n  if (baseQueue !== null) {\n    const first = baseQueue.next;\n    let newState = current.baseState;\n\n    let newBaseState = null;\n    let newBaseQueueFirst = null;\n    let newBaseQueueLast = null;\n    let update = first;\n\n    do {\n      const updateLane = update.lane;\n      // 3.1 优先级提取update\n      if (!isSubsetOfLanes(renderLanes, updateLane)) {\n        // 优先级不够: 加入到baseQueue中, 等待下一次render\n        const clone: Update<S, A> = {\n          lane: updateLane,\n          action: update.action,\n          eagerReducer: update.eagerReducer,\n          eagerState: update.eagerState,\n          next: (null: any),\n        };\n        if (newBaseQueueLast === null) {\n          newBaseQueueFirst = newBaseQueueLast = clone;\n          newBaseState = newState;\n        } else {\n          newBaseQueueLast = newBaseQueueLast.next = clone;\n        }\n        currentlyRenderingFiber.lanes = mergeLanes(\n          currentlyRenderingFiber.lanes,\n          updateLane,\n        );\n        markSkippedUpdateLanes(updateLane);\n      } else {\n        // 优先级足够: 状态合并\n        if (newBaseQueueLast !== null) {\n          // 更新baseQueue\n          const clone: Update<S, A> = {\n            lane: NoLane,\n            action: update.action,\n            eagerReducer: update.eagerReducer,\n            eagerState: update.eagerState,\n            next: (null: any),\n          };\n          newBaseQueueLast = newBaseQueueLast.next = clone;\n        }\n        if (update.eagerReducer === reducer) {\n          // 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer\n          newState = ((update.eagerState: any): S);\n        } else {\n          const action = update.action;\n          // 调用reducer获取最新状态\n          newState = reducer(newState, action);\n        }\n      }\n      update = update.next;\n    } while (update !== null && update !== first);\n\n    // 3.2. 更新属性\n    if (newBaseQueueLast === null) {\n      newBaseState = newState;\n    } else {\n      newBaseQueueLast.next = (newBaseQueueFirst: any);\n    }\n    if (!is(newState, hook.memoizedState)) {\n      markWorkInProgressReceivedUpdate();\n    }\n    // 把计算之后的结果更新到workInProgressHook上\n    hook.memoizedState = newState;\n    hook.baseState = newBaseState;\n    hook.baseQueue = newBaseQueueLast;\n    queue.lastRenderedState = newState;\n  }\n\n  const dispatch: Dispatch<A> = (queue.dispatch: any);\n  return [hook.memoizedState, dispatch];\n}\n```\n\n`updateReducer`函数, 代码相对较长, 但是逻辑分明:\n\n1. 调用`updateWorkInProgressHook`获取`workInProgressHook`对象\n2. 链表拼接: 将 `hook.queue.pending` 拼接到 `current.baseQueue`\n\n   ![](../../snapshots/hook-state/after-basequeue-combine.png)\n\n3. 状态计算\n\n   1. `update`优先级不够: 加入到 baseQueue 中, 等待下一次 render\n   2. `update`优先级足够: 状态合并\n   3. 更新属性\n\n      ![](../../snapshots/hook-state/state-compute.png)\n\n### 性能优化\n\n`dispatchAction`函数中, 在调用`scheduleUpdateOnFiber`之前, 针对`update`对象做了性能优化.\n\n1. `queue.pending`中只包含当前`update`时, 即当前`update`是`queue.pending`中的第一个`update`\n2. 直接调用`queue.lastRenderedReducer`,计算出`update`之后的 state, 记为`eagerState`\n3. 如果`eagerState`与`currentState`相同, 则直接退出, 不用发起调度更新.\n4. 已经被挂载到`queue.pending`上的`update`会在下一次`render`时再次合并.\n\n```js\nfunction dispatchAction<S, A>(\n  fiber: Fiber,\n  queue: UpdateQueue<S, A>,\n  action: A,\n) {\n  // ...省略无关代码 ...只保留性能优化部分代码:\n\n  // 下面这个if判断, 能保证当前创建的update, 是`queue.pending`中第一个`update`. 为什么? 发起更新之后fiber.lanes会被改动(可以回顾`fiber 树构造(对比更新)`章节), 如果`fiber.lanes && alternate.lanes`没有被改动, 自然就是首个update\n  if (\n    fiber.lanes === NoLanes &&\n    (alternate === null || alternate.lanes === NoLanes)\n  ) {\n    const lastRenderedReducer = queue.lastRenderedReducer;\n    if (lastRenderedReducer !== null) {\n      let prevDispatcher;\n      const currentState: S = (queue.lastRenderedState: any);\n      const eagerState = lastRenderedReducer(currentState, action);\n      // 暂存`eagerReducer`和`eagerState`, 如果在render阶段reducer==update.eagerReducer, 则可以直接使用无需再次计算\n      update.eagerReducer = lastRenderedReducer;\n      update.eagerState = eagerState;\n      if (is(eagerState, currentState)) {\n        // 快速通道, eagerState与currentState相同, 无需调度更新\n        // 注: update已经被添加到了queue.pending, 并没有丢弃. 之后需要更新的时候, 此update还是会起作用\n        return;\n      }\n    }\n  }\n  // 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.\n  scheduleUpdateOnFiber(fiber, lane, eventTime);\n}\n```\n\n为了验证上述优化, 可以查看这个 demo:[![Edit hook-throttle](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/hook-throttle-58ly5?fontsize=14&hidenavigation=1&theme=dark)\n\n### 异步更新\n\n上述示例都是为在`Legacy`模式下, 所以均为同步更新. 所以`update`对象会被全量合并,`hook.baseQueue`和`hook.baseState`并没有起到实质作用.\n\n虽然在`v17.x`版本中, 并没有`Concurrent`模式的入口, 即将发布的`v18.x`版本将全面进入异步时代, 所以本节提前梳理一下`update`异步合并的逻辑. 同时加深`hook.baseQueue`和`hook.baseState`的理解.\n\n假设有一个`queue.pending`链表, 其中`update`优先级不同, `绿色`表示高优先级, `灰色`表示低优先级, `红色`表示最高优先级.\n\n在执行`updateReducer`之前, `hook.memoizedState`有如下结构(其中`update3, update4`是低优先级):\n\n![](../../snapshots/hook-state/async-update-before-combine.png)\n\n链表拼接:\n\n- 和同步更新时一致, 直接把`queue.pending`拼接到`current.baseQueue`\n\n![](../../snapshots/hook-state/async-update-after-combine.png)\n\n状态计算:\n\n- 只会提取`update1, update2`这 2 个高优先级的`update`, 所以最后`memoizedState=2`\n- 保留其余低优先级的`update`, 等待下一次`render`\n- 从第一个低优先级`update3`开始, 随后的所有`update`都会被添加到`baseQueue`, 由于`update2`已经是高优先级, 会设置`update2.lane=NoLane`将优先级升级到最高(红色表示).\n- 而`baseState`代表第一个低优先级`update3`之前的`state`, 在本例中, `baseState=1`\n\n![](../../snapshots/hook-state/async-update-state-compute.png)\n\n`function`节点被处理完后, 高优先级的`update`, 会率先被使用(`memoizedState=2`). 一段时间后, 低优先级`update3, update4`符合渲染, 这种情况下再次执行`updateReducer`重复之前的步骤.\n\n链表拼接:\n\n- 由于`queue.pending = null`, 故拼接前后没有实质变化\n\n![](../../snapshots/hook-state/async-final-combine.png)\n\n状态计算:\n\n- 现在所有`update.lane`都符合`渲染优先级`, 所以最后的内存结构与同步更新一致(`memoizedState=4,baseState=4`).\n\n![](../../snapshots/hook-state/async-final-compute.png)\n\n> 结论: 尽管`update`链表的优先级不同, 中间的`render`可能有多次, 但最终的更新结果等于`update`链表`按顺序合并`.\n\n## 总结\n\n本节深入分析`状态Hook`即`useState`的内部原理, 从`同步,异步`更新理解了`update`对象的合并方式, 最终结果存储在`hook.memoizedState`供给`function`使用.\n"
  },
  {
    "path": "docs/main/hook-summary.md",
    "content": "---\ntitle: Hook 原理(概览)\ngroup: 状态管理\norder: 1\n---\n\n# Hook 原理(概览)\n\n在前文[状态与副作用](./state-effects.md)中, 总结了`class组件, function组件`中通过`api`去改变`fiber节点`的`状态`和`副作用`. 其中对于`function组件`来讲, 其内部则需要依靠`Hook`来实现.\n\n官方文档上专门用了一个版块来介绍[Hook](https://zh-hans.reactjs.org/docs/hooks-intro.html), 这里摘抄了几个比较关心的问题(其他`FAQ`请移步官网):\n\n1. [引入`Hook`的动机?](https://zh-hans.reactjs.org/docs/hooks-intro.html#motivation)\n\n   - 在组件之间复用状态逻辑很难; 复杂组件变得难以理解; 难以理解的 class. 为了解决这些实际开发痛点, 引入了`Hook`.\n\n2. [`Hook` 是什么? 什么时候会用 `Hook`?](https://zh-hans.reactjs.org/docs/hooks-state.html#whats-a-hook)\n\n   - `Hook` 是一个特殊的函数, 它可以让你“钩入” `React` 的特性. 如, `useState` 是允许你在 `React` 函数组件中添加 `state` 的 `Hook`.\n   - 如果你在编写函数组件并意识到需要向其添加一些 `state`, 以前的做法是必须将其转化为 `class`. 现在你可以在现有的函数组件中使用 `Hook`.\n\n3. [`Hook` 会因为在渲染时创建函数而变慢吗?](https://zh-hans.reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render)\n   - 不会. 在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别. 除此之外,可以认为 `Hook` 的设计在某些方面更加高效:\n     - `Hook` 避免了 `class` 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本.\n     - 符合语言习惯的代码在使用 `Hook` 时不需要很深的组件树嵌套. 这个现象在使用`高阶组件`、`render props`、和 `context` 的代码库中非常普遍. 组件树小了, `React` 的工作量也随之减少.\n\n所以`Hook`是`React`团队在大量实践后的产物, 更优雅的代替`class`, 且性能更高. 故从开发使用者的角度来讲, 应该拥抱`Hook`所带来的便利.\n\n## Hook 与 Fiber\n\n通过官网文档的讲解, 能快速掌握`Hook`的使用. 再结合前文[状态与副作用](./state-effects.md)的介绍, 我们知道使用`Hook`最终也是为了控制`fiber节点`的`状态`和`副作用`. 从`fiber`视角, 状态和副作用相关的属性如下(这里不再解释单个属性的意义, 可以回顾[状态与副作用](./state-effects.md)):\n\n```js\nexport type Fiber = {|\n  // 1. fiber节点自身状态相关\n  pendingProps: any,\n  memoizedProps: any,\n  updateQueue: mixed,\n  memoizedState: any,\n\n  // 2. fiber节点副作用(Effect)相关\n  flags: Flags,\n  nextEffect: Fiber | null,\n  firstEffect: Fiber | null,\n  lastEffect: Fiber | null,\n|};\n```\n\n使用`Hook`的任意一个`api`, 最后都是为了控制上述这几个`fiber属性`.\n\n## Hook 数据结构\n\n在[ReactFiberHooks](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L95-L140)中, 定义了`Hook`的数据结构:\n\n```js\ntype Update<S, A> = {|\n  lane: Lane,\n  action: A,\n  eagerReducer: ((S, A) => S) | null,\n  eagerState: S | null,\n  next: Update<S, A>,\n  priority?: ReactPriorityLevel,\n|};\n\ntype UpdateQueue<S, A> = {|\n  pending: Update<S, A> | null,\n  dispatch: ((A) => mixed) | null,\n  lastRenderedReducer: ((S, A) => S) | null,\n  lastRenderedState: S | null,\n|};\n\nexport type Hook = {|\n  memoizedState: any, // 当前状态\n  baseState: any, // 基状态\n  baseQueue: Update<any, any> | null, // 基队列\n  queue: UpdateQueue<any, any> | null, // 更新队列\n  next: Hook | null, // next指针\n|};\n```\n\n从定义来看, `Hook`对象共有 5 个属性(有关这些属性的应用, 将在`Hook 原理(状态)`章节中具体分析.):\n\n1. `hook.memoizedState`: 保持在内存中的局部状态.\n2. `hook.baseState`: `hook.baseQueue`中所有`update`对象合并之后的状态.\n3. `hook.baseQueue`: 存储`update对象`的环形链表, 只包括高于本次渲染优先级的`update对象`.\n4. `hook.queue`: 存储`update对象`的环形链表, 包括所有优先级的`update对象`.\n5. `hook.next`: `next`指针, 指向链表中的下一个`hook`.\n\n所以`Hook`是一个链表, 单个`Hook`拥有自己的状态`hook.memoizedState`和自己的更新队列`hook.queue`(有关 Hook 状态的分析, 在`Hook原理(状态)`章节中解读).\n\n![](../../snapshots/hook-summary/hook-linkedlist.png)\n\n注意: 其中`hook.queue`与`fiber.updateQueue`虽然都是`update环形链表`, 尽管`update对象`的数据结构与处理方式都高度相似, 但是这 2 个队列中的`update对象`是完全独立的. `hook.queue`只作用于`hook对象`的状态维护, 切勿与`fiber.updateQueue`混淆.\n\n## Hook 分类\n\n在`v17.0.2`中, 共定义了[14 种 Hook](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L111-L125)\n\n```js\nexport type HookType =\n  | 'useState'\n  | 'useReducer'\n  | 'useContext'\n  | 'useRef'\n  | 'useEffect'\n  | 'useLayoutEffect'\n  | 'useCallback'\n  | 'useMemo'\n  | 'useImperativeHandle'\n  | 'useDebugValue'\n  | 'useDeferredValue'\n  | 'useTransition'\n  | 'useMutableSource'\n  | 'useOpaqueIdentifier';\n```\n\n官网上已经将其分为了 2 个类别, 状态`Hook`(`State Hook`), 和副作用`Hook`(`Effect Hook`).\n\n这里我们可以结合前文[状态与副作用](./state-effects.md), 从`fiber`的视角去理解`状态Hook`与`副作用Hook`的区别.\n\n### 状态 Hook\n\n狭义上讲, `useState, useReducer`可以在`function组件`添加内部的`state`, 且`useState`实际上是`useReducer`的简易封装, 是一个最特殊(简单)的`useReducer`. 所以将`useState, useReducer`称为`状态Hook`.\n\n广义上讲, 只要能实现数据持久化`且没有副作用`的`Hook`, 均可以视为`状态Hook`, 所以还包括`useContext, useRef, useCallback, useMemo`等. 这类`Hook`内部没有使用`useState/useReducer`, 但是它们也能实现多次`render`时, 保持其初始值不变(即数据持久化)且没有任何`副作用`.\n\n得益于[双缓冲技术(double buffering)](./fibertree-prepare.md#双缓冲技术), 在多次`render`时, 以`fiber`为载体, 保证复用同一个`Hook`对象, 进而实现数据持久化. 具体实现细节, 在`Hook原理(状态)`章节中讨论.\n\n### 副作用 Hook\n\n回到`fiber`视角, `状态Hook`实现了状态持久化(等同于`class组件`维护`fiber.memoizedState`), 那么`副作用Hook`则会修改`fiber.flags`. (通过前文`fiber树构造`系列的解读, 我们知道在`performUnitOfWork->completeWork`阶段, 所有存在副作用的`fiber`节点, 都会被添加到父节点的`副作用队列`后, 最后在`commitRoot`阶段处理这些`副作用节点`.)\n\n另外, `副作用Hook`还提供了`副作用回调`(类似于`class组件`的生命周期回调), 比如:\n\n```js\n// 使用useEffect时, 需要传入一个副作用回调函数.\n// 在fiber树构造完成之后, commitRoot阶段会处理这些副作用回调\nuseEffect(() => {\n  console.log('这是一个副作用回调函数');\n}, []);\n```\n\n在`react`内部, `useEffect`就是最标准的`副作用Hook`. 其他比如`useLayoutEffect`以及`自定义Hook`, 如果要实现`副作用`, 必须直接或间接的调用`useEffect`.\n\n有关`useEffect`具体实现细节, 在`Hook原理(副作用)`章节中讨论.\n\n### 组合 Hook\n\n虽然官网并无`组合Hook`的说法, 但事实上大多数`Hook`(包括自定义`Hook`)都是由上述 2 种 `Hook`组合而成, 同时拥有这 2 种 Hook 的特性.\n\n- 在`react`内部有`useDeferredValue, useTransition, useMutableSource, useOpaqueIdentifier`等.\n- 平时开发中, `自定义Hook`大部分都是组合 Hook.\n\n比如官网上的[自定义 Hook](https://zh-hans.reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook)例子:\n\n```js\nimport { useState, useEffect } from 'react';\n\nfunction useFriendStatus(friendID) {\n  // 1. 调用useState, 创建一个状态Hook\n  const [isOnline, setIsOnline] = useState(null);\n\n  // 2. 调用useEffect, 创建一个副作用Hook\n  useEffect(() => {\n    function handleStatusChange(status) {\n      setIsOnline(status.isOnline);\n    }\n    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);\n    return () => {\n      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);\n    };\n  });\n  return isOnline;\n}\n```\n\n## 调用 function 前\n\n在调用`function`之前, `react`内部还需要提前做一些准备工作.\n\n### 处理函数\n\n从`fiber树构造`的视角来看, 不同的`fiber`类型, 只需要调用不同的`处理函数`返回`fiber子节点`. 所以在[performUnitOfWork->beginWork](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L3340-L3354)函数中, 调用了多种`处理函数`. 从调用方来讲, 无需关心`处理函数`的内部实现(比如`updateFunctionComponent`内部使用了`Hook对象`, `updateClassComponent`内部使用了`class实例`).\n\n本节讨论`Hook`, 所以列出其中的[updateFunctionComponent](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberBeginWork.old.js#L702-L783)函数:\n\n```js\n// 只保留FunctionComponent相关:\nfunction beginWork(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  renderLanes: Lanes,\n): Fiber | null {\n  const updateLanes = workInProgress.lanes;\n  switch (workInProgress.tag) {\n    case FunctionComponent: {\n      const Component = workInProgress.type;\n      const unresolvedProps = workInProgress.pendingProps;\n      const resolvedProps =\n        workInProgress.elementType === Component\n          ? unresolvedProps\n          : resolveDefaultProps(Component, unresolvedProps);\n      return updateFunctionComponent(\n        current,\n        workInProgress,\n        Component,\n        resolvedProps,\n        renderLanes,\n      );\n    }\n  }\n}\n\nfunction updateFunctionComponent(\n  current,\n  workInProgress,\n  Component,\n  nextProps: any,\n  renderLanes,\n) {\n  // ...省略无关代码\n  let context;\n  let nextChildren;\n  prepareToReadContext(workInProgress, renderLanes);\n\n  // 进入Hooks相关逻辑, 最后返回下级ReactElement对象\n  nextChildren = renderWithHooks(\n    current,\n    workInProgress,\n    Component,\n    nextProps,\n    context,\n    renderLanes,\n  );\n  // 进入reconcile函数, 生成下级fiber节点\n  reconcileChildren(current, workInProgress, nextChildren, renderLanes);\n  // 返回下级fiber节点\n  return workInProgress.child;\n}\n```\n\n在`updateFunctionComponent`函数中调用了[renderWithHooks](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L342-L476)(位于[ReactFiberHooks](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js)) , 至此`Fiber`与`Hook`产生了关联.\n\n### 全局变量\n\n在分析`renderWithHooks`函数前, 有必要理解[ReactFiberHooks](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js)头部定义的全局变量(源码中均有英文注释):\n\n```js\n// 渲染优先级\nlet renderLanes: Lanes = NoLanes;\n\n// 当前正在构造的fiber, 等同于 workInProgress, 为了和当前hook区分, 所以将其改名\nlet currentlyRenderingFiber: Fiber = (null: any);\n\n// Hooks被存储在fiber.memoizedState 链表上\nlet currentHook: Hook | null = null; // currentHook = fiber(current).memoizedState\n\nlet workInProgressHook: Hook | null = null; // workInProgressHook = fiber(workInProgress).memoizedState\n\n// 在function的执行过程中, 是否再次发起了更新. 只有function被完全执行之后才会重置.\n// 当render异常时, 通过该变量可以决定是否清除render过程中的更新.\nlet didScheduleRenderPhaseUpdate: boolean = false;\n\n// 在本次function的执行过程中, 是否再次发起了更新. 每一次调用function都会被重置\nlet didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;\n\n// 在本次function的执行过程中, 重新发起更新的最大次数\nconst RE_RENDER_LIMIT = 25;\n```\n\n每个变量的解释, 可以对照源码中的英文注释, 其中最重要的有:\n\n1. `currentlyRenderingFiber`: 当前正在构造的 fiber, 等同于 workInProgress\n2. `currentHook 与 workInProgressHook`: 分别指向`current.memoizedState`和`workInProgress.memoizedState`\n\n注: 有关`current`和`workInProgress`的区别, 请回顾[双缓冲技术(double buffering)](./fibertree-prepare.md#双缓冲技术)\n\n### renderWithHooks 函数\n\n[renderWithHooks](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L342-L476)源码看似较长, 但是去除 dev 后保留主干, 逻辑十分清晰. 以调用`function`为分界点, 逻辑被分为 3 个部分:\n\n```js\n// ...省略无关代码\nexport function renderWithHooks<Props, SecondArg>(\n  current: Fiber | null,\n  workInProgress: Fiber,\n  Component: (p: Props, arg: SecondArg) => any,\n  props: Props,\n  secondArg: SecondArg,\n  nextRenderLanes: Lanes,\n): any {\n  // --------------- 1. 设置全局变量 -------------------\n  renderLanes = nextRenderLanes; // 当前渲染优先级\n  currentlyRenderingFiber = workInProgress; // 当前fiber节点, 也就是function组件对应的fiber节点\n\n  // 清除当前fiber的遗留状态\n  workInProgress.memoizedState = null;\n  workInProgress.updateQueue = null;\n  workInProgress.lanes = NoLanes;\n\n  // --------------- 2. 调用function,生成子级ReactElement对象 -------------------\n  // 指定dispatcher, 区分mount和update\n  ReactCurrentDispatcher.current =\n    current === null || current.memoizedState === null\n      ? HooksDispatcherOnMount\n      : HooksDispatcherOnUpdate;\n  // 执行function函数, 其中进行分析Hooks的使用\n  let children = Component(props, secondArg);\n\n  // --------------- 3. 重置全局变量,并返回 -------------------\n  // 执行function之后, 还原被修改的全局变量, 不影响下一次调用\n  renderLanes = NoLanes;\n  currentlyRenderingFiber = (null: any);\n\n  currentHook = null;\n  workInProgressHook = null;\n  didScheduleRenderPhaseUpdate = false;\n\n  return children;\n}\n```\n\n1. 调用`function`前: 设置全局变量, 标记`渲染优先级`和当前`fiber`, 清除当前`fiber`的遗留状态.\n2. 调用`function`: 构造出`Hooks`链表, 最后生成子级`ReactElement`对象(`children`).\n3. 调用`function`后: 重置全局变量, 返回`children`.\n   - 为了保证不同的`function`节点在调用时`renderWithHooks`互不影响, 所以退出时重置全局变量.\n\n## 调用 function\n\n### Hooks 构造\n\n在`function`中, 如果使用了`Hook api`(如: `useEffect`, `useState`), 就会创建一个与之对应的`Hook`对象, 接下来重点分析这个创建过程.\n\n有如下 demo:\n[![Edit hook-summary](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/hook-summary-wb7zz?fontsize=14&hidenavigation=1&theme=dark)\n\n```jsx\nimport React, { useState, useEffect } from 'react';\nexport default function App() {\n  // 1. useState\n  const [a, setA] = useState(1);\n  // 2. useEffect\n  useEffect(() => {\n    console.log(`effect 1 created`);\n  });\n  // 3. useState\n  const [b] = useState(2);\n  // 4. useEffect\n  useEffect(() => {\n    console.log(`effect 2 created`);\n  });\n  return (\n    <>\n      <button onClick={() => setA(a + 1)}>{a}</button>\n      <button>{b}</button>\n    </>\n  );\n}\n```\n\n在`function`组件中, 同时使用了`状态Hook`和`副作用Hook`.\n\n初次渲染时, 逻辑执行到`performUnitOfWork->beginWork->updateFunctionComponent->renderWithHooks`前, 内存结构如下(本节重点是`Hook`, 有关`fiber树构造`过程可回顾前文):\n\n![](../../snapshots/hook-summary/mount-before-renderwithhooks.png)\n\n当执行`renderWithHooks`时, 开始调用`function`. 本例中, 在`function`内部, 共使用了 4 次`Hook api`, 依次调用`useState, useEffect, useState, useEffect`.\n\n而`useState, useEffect`在`fiber`初次构造时分别对应[mountState](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1113-L1136)和[mountEffect->mountEffectImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1193-L1248)\n\n```js\nfunction mountState<S>(\n  initialState: (() => S) | S,\n): [S, Dispatch<BasicStateAction<S>>] {\n  const hook = mountWorkInProgressHook();\n  // ...省略部分本节不讨论\n  return [hook.memoizedState, dispatch];\n}\n\nfunction mountEffectImpl(fiberFlags, hookFlags, create, deps): void {\n  const hook = mountWorkInProgressHook();\n  // ...省略部分本节不讨论\n}\n```\n\n无论`useState, useEffect`, 内部都通过`mountWorkInProgressHook`创建一个 hook.\n\n### 链表存储\n\n而[mountWorkInProgressHook](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L531-L550)非常简单:\n\n```js\nfunction mountWorkInProgressHook(): Hook {\n  const hook: Hook = {\n    memoizedState: null,\n\n    baseState: null,\n    baseQueue: null,\n    queue: null,\n\n    next: null,\n  };\n\n  if (workInProgressHook === null) {\n    // 链表中首个hook\n    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;\n  } else {\n    // 将hook添加到链表末尾\n    workInProgressHook = workInProgressHook.next = hook;\n  }\n  return workInProgressHook;\n}\n```\n\n逻辑是创建`Hook`并挂载到`fiber.memoizedState`上, 多个`Hook`以链表结构保存.\n\n本示例中, `function`调用之后则会创建 4 个`hook`, 这时的内存结构如下:\n\n![](../../snapshots/hook-summary/mount-after-renderwithhooks.png)\n\n可以看到: 无论`状态Hook`或`副作用Hook`都按照调用顺序存储在`fiber.memoizedState`链表中.\n\n![](../../snapshots/hook-summary/mount-fiber-memoizedstate.png)\n\n### 顺序克隆\n\n`fiber树构造(对比更新)`阶段, 执行`updateFunctionComponent->renderWithHooks`时再次调用`function`, `调用function前`的内存结构如下:\n\n![](../../snapshots/hook-summary/update-before-renderwithhooks.png)\n\n注意: 在`renderWithHooks`函数中已经设置了`workInProgress.memoizedState = null`, 等待调用`function`时重新设置.\n\n接下来调用`function`, 同样依次调用`useState, useEffect, useState, useEffect`. 而`useState, useEffect`在`fiber`对比更新时分别对应[updateState->updateReducer](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1138-L1142)和[updateEffect->updateEffectImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L1205-L1266)\n\n```js\n// ----- 状态Hook --------\nfunction updateReducer<S, I, A>(\n  reducer: (S, A) => S,\n  initialArg: I,\n  init?: (I) => S,\n): [S, Dispatch<A>] {\n  const hook = updateWorkInProgressHook();\n  // ...省略部分本节不讨论\n}\n\n// ----- 副作用Hook --------\nfunction updateEffectImpl(fiberFlags, hookFlags, create, deps): void {\n  const hook = updateWorkInProgressHook();\n  // ...省略部分本节不讨论\n}\n```\n\n无论`useState, useEffect`, 内部调用`updateWorkInProgressHook`获取一个 hook.\n\n```js\nfunction updateWorkInProgressHook(): Hook {\n  // 1. 移动currentHook指针\n  let nextCurrentHook: null | Hook;\n  if (currentHook === null) {\n    const current = currentlyRenderingFiber.alternate;\n    if (current !== null) {\n      nextCurrentHook = current.memoizedState;\n    } else {\n      nextCurrentHook = null;\n    }\n  } else {\n    nextCurrentHook = currentHook.next;\n  }\n\n  // 2. 移动workInProgressHook指针\n  let nextWorkInProgressHook: null | Hook;\n  if (workInProgressHook === null) {\n    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;\n  } else {\n    nextWorkInProgressHook = workInProgressHook.next;\n  }\n\n  if (nextWorkInProgressHook !== null) {\n    // 渲染时更新: 本节不讨论\n  } else {\n    currentHook = nextCurrentHook;\n    // 3. 克隆currentHook作为新的workInProgressHook.\n    // 随后逻辑与mountWorkInProgressHook一致\n    const newHook: Hook = {\n      memoizedState: currentHook.memoizedState,\n\n      baseState: currentHook.baseState,\n      baseQueue: currentHook.baseQueue,\n      queue: currentHook.queue,\n\n      next: null, // 注意next指针是null\n    };\n    if (workInProgressHook === null) {\n      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;\n    } else {\n      workInProgressHook = workInProgressHook.next = newHook;\n    }\n  }\n  return workInProgressHook;\n}\n```\n\n`updateWorkInProgressHook`函数逻辑简单: 目的是为了让`currentHook`和`workInProgressHook`两个指针同时向后移动.\n\n1. 由于`renderWithHooks函数`设置了`workInProgress.memoizedState=null`, 所以`workInProgressHook`初始值必然为`null`, 只能从`currentHook`克隆.\n2. 而从`currentHook`克隆而来的`newHook.next=null`, 进而导致`workInProgressHook`链表需要完全重建.\n\n所以`function`执行完成之后, 有关`Hook`的内存结构如下:\n\n![](../../snapshots/hook-summary/update-after-renderwithhooks.png)\n\n可以看到:\n\n1. 以双缓冲技术为基础, 将`current.memoizedState`按照顺序克隆到了`workInProgress.memoizedState`中.\n2. `Hook`经过了一次克隆, 内部的属性(`hook.memoizedState`等)都没有变动, 所以其状态并不会丢失.\n\n![](../../snapshots/hook-summary/update-fiber-memoizedstate.png)\n\n## 总结\n\n本节首先引入了官方文档上对于`Hook`的解释, 了解`Hook`的由来, 以及`Hook`相较于`class`的优势. 然后从`fiber`视角分析了`fiber`与`hook`的内在关系, 通过`renderWithHooks`函数, 把`Hook`链表挂载到了`fiber.memoizedState`之上. 利用`fiber树`内部的双缓冲技术, 实现了`Hook`从`current`到`workInProgress`转移, 进而实现了`Hook`状态的持久化.\n"
  },
  {
    "path": "docs/main/macro-structure.md",
    "content": "---\ntitle: 宏观包结构\nnav:\n  title: 原理解析\n  order: 0\ngroup:\n  title: 基本概念\n  order: 0\norder: 0\n---\n\n# React 应用的宏观包结构(web 开发)\n\n> React 工程目录的 packages 下包含 35 个包([`@17.0.2`版本](https://github.com/facebook/react/tree/v17.0.2)).\n> 其中与`web`开发相关的核心包共有 4 个, 本系列近 20 篇文章, 以这 4 个包为线索进行展开, 深入理解 react 内部作用原理.\n\n## 基础包结构\n\n1. react\n\n   > react 基础包, 只提供定义 react 组件(`ReactElement`)的必要函数, 一般来说需要和渲染器(`react-dom`,`react-native`)一同使用. 在编写`react`应用的代码时, 大部分都是调用此包的 api.\n\n2. react-dom\n\n   > react 渲染器之一, 是 react 与 web 平台连接的桥梁(可以在浏览器和 nodejs 环境中使用), 将`react-reconciler`中的运行结果输出到 web 界面上. 在编写`react`应用的代码时,大多数场景下, 能用到此包的就是一个入口函数`ReactDOM.render(<App/>, document.getElementById('root'))`, 其余使用的 api, 基本是`react`包提供的.\n\n3. react-reconciler\n\n   > react 得以运行的核心包(综合协调`react-dom`,`react`,`scheduler`各包之间的调用与配合).\n   > 管理 react 应用状态的输入和结果的输出. 将输入信号最终转换成输出信号传递给渲染器.\n\n   - 接受输入(`scheduleUpdateOnFiber`), 将`fiber`树生成逻辑封装到一个回调函数中(涉及`fiber`树形结构, `fiber.updateQueue`队列, 调和算法等),\n   - 把此回调函数(`performSyncWorkOnRoot`或`performConcurrentWorkOnRoot`)送入`scheduler`进行调度\n   - `scheduler`会控制回调函数执行的时机, 回调函数执行完成后得到全新的 fiber 树\n   - 再调用渲染器(如`react-dom`, `react-native`等)将 fiber 树形结构最终反映到界面上\n\n4. scheduler\n\n   > 调度机制的核心实现, 控制由`react-reconciler`送入的回调函数的执行时机, 在`concurrent`模式下可以实现任务分片. 在编写`react`应用的代码时, 同样几乎不会直接用到此包提供的 api.\n\n   - 核心任务就是执行回调(回调函数由`react-reconciler`提供)\n   - 通过控制回调函数的执行时机, 来达到任务分片的目的, 实现可中断渲染(`concurrent`模式下才有此特性)\n\n## 宏观总览\n\n### 架构分层\n\n为了便于理解, 可将 react 应用整体结构分为接口层(`api`)和内核层(`core`)2 个部分\n\n1. 接口层(api)\n   `react`包, 平时在开发过程中使用的绝大部分`api`均来自此包(不是所有). 在`react`启动之后, 正常可以改变渲染的基本操作有 3 个.\n\n   - class 组件中使用`setState()`\n   - function 组件里面使用 hook,并发起`dispatchAction`去改变 hook 对象\n   - 改变 context(其实也需要`setState`或`dispatchAction`的辅助才能改变)\n\n   以上`setState`和`dispatchAction`都由`react`包直接暴露. 所以要想 react 工作, 基本上是调用`react`包的 api 去与其他包进行交互.\n\n2. 内核层(core)\n   整个内核部分, 由 3 部分构成:\n   1. 调度器\n      `scheduler`包, 核心职责只有 1 个, 就是执行回调.\n      - 把`react-reconciler`提供的回调函数, 包装到一个任务对象中.\n      - 在内部维护一个任务队列, 优先级高的排在最前面.\n      - 循环消费任务队列, 直到队列清空.\n   2. 构造器\n      `react-reconciler`包, 有 3 个核心职责:\n      1. 装载渲染器, 渲染器必须实现[`HostConfig`协议](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/README.md#practical-examples)(如: `react-dom`), 保证在需要的时候, 能够正确调用渲染器的 api, 生成实际节点(如: `dom`节点).\n      2. 接收`react-dom`包(初次`render`)和`react`包(后续更新`setState`)发起的更新请求.\n      3. 将`fiber`树的构造过程包装在一个回调函数中, 并将此回调函数传入到`scheduler`包等待调度.\n   3. 渲染器\n      `react-dom`包, 有 2 个核心职责:\n      1. 引导`react`应用的启动(通过`ReactDOM.render`).\n      2. 实现[`HostConfig`协议](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/README.md#practical-examples)([源码在 ReactDOMHostConfig.js 中](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMHostConfig.js)), 能够将`react-reconciler`包构造出来的`fiber`树表现出来, 生成 dom 节点(浏览器中), 生成字符串(ssr).\n\n注意:\n\n- 此处分层的标准并非官方说法, 因为官方没有`架构分层`这样的术语.\n- 本文只是为了深入理解 react, 在官方标准之外, 对其进行分解和剖析, 方便我们理解 react 架构.\n\n### 内核关系\n\n现将内核 3 个包的主要职责和调用关系, 绘制到一张概览图上:\n\n![](../../snapshots/macro-structure/core-packages.png)\n\n注意:\n\n- 红色方块代表入口函数, 绿色方块代表出口函数.\n- package 之间的调用脉络就是通过板块间的入口和出口函数连接起来的.\n\n通过此概览图, 基本可以表述 react 内核层的宏观结构. 后面的章节, 会按照此图的思路深入到对应的模块逐一解读.\n\n## 总结\n\n本文从宏观架构的角度, 阐述了`react`核心包之间的依赖和调用关系, 使读者对`react`架构有简单的认识. 另外也给读者提供一个阅读源码的思路, 先整体浏览, 再深入分析, 各个击破.\n"
  },
  {
    "path": "docs/main/object-structure.md",
    "content": "---\ntitle: 高频对象\ngroup: 基本概念\norder: 2\n---\n\n# React 应用中的高频对象\n\n在 React 应用中, 有很多特定的对象或数据结构. 了解这些内部的设计, 可以更容易理解 react 运行原理. 本章主要列举从 react 启动到渲染过程出现频率较高, 影响范围较大的对象, 它们贯穿整个 react 运行时.\n\n其他过程的重要对象:\n\n- 如`事件对象`(位于`react-dom/events`保障 react 应用能够响应 ui 交互), 在事件机制章节中详细解读.\n- 如`ReactContext, ReactProvider, ReactConsumer`对象, 在 context 机制章节中详细解读.\n\n## react 包\n\n在[React 应用的宏观包结构](./macro-structure.md)中介绍过, 此包定义 react 组件(`ReactElement`)的必要函数, 提供一些操作`ReactElement`对象的 api.\n\n所以这个包的核心需要理解`ReactElement`对象, 假设有如下入口函数:\n\n```js\n// 入口函数\nReactDOM.render(<App />, document.getElementById('root'));\n```\n\n可以简单的认为, 包括`<App/>`及其所有子节点都是`ReactElement`对象(在 render 之后才会生成子节点, 后文详细解读), 每个`ReactElement`对象的区别在于 type 不同.\n\n### [ReactElement 对象](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactElement.js#L126-L146)\n\n> 其 type 定义在[`shared`包中](https://github.com/facebook/react/blob/v17.0.2/packages/shared/ReactElementType.js#L15).\n\n所有采用`jsx`语法书写的节点, 都会被编译器转换, 最终会以`React.createElement(...)`的方式, 创建出来一个与之对应的`ReactElement`对象.\n\n`ReactElement`对象的数据结构如下:\n\n```ts\nexport type ReactElement = {|\n  // 用于辨别ReactElement对象\n  $$typeof: any,\n\n  // 内部属性\n  type: any, // 表明其种类\n  key: any,\n  ref: any,\n  props: any,\n\n  // ReactFiber 记录创建本对象的Fiber节点, 还未与Fiber树关联之前, 该属性为null\n  _owner: any,\n\n  // __DEV__ dev环境下的一些额外信息, 如文件路径, 文件名, 行列信息等\n  _store: {validated: boolean, ...},\n  _self: React$Element<any>,\n  _shadowChildren: any,\n  _source: Source,\n|};\n\n```\n\n需要特别注意 2 个属性:\n\n1. `key`属性在`reconciler`阶段会用到, 目前只需要知道所有的`ReactElement`对象都有 key 属性(且[其默认值是 null](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactElement.js#L348-L357), 这点十分重要, 在 diff 算法中会使用到).\n\n2. `type`属性决定了节点的种类:\n\n- 它的值可以是字符串(代表`div,span`等 dom 节点), 函数(代表`function, class`等节点), 或者 react 内部定义的节点类型(`portal,context,fragment`等)\n- 在`reconciler`阶段, 会根据 type 执行不同的逻辑(在 fiber 构建阶段详细解读).\n  - 如 type 是一个字符串类型, 则直接使用.\n  - 如 type 是一个`ReactComponent`类型, 则会调用其 render 方法获取子节点.\n  - 如 type 是一个`function`类型,则会调用该方法获取子节点\n  - ...\n\n在`v17.0.2`中, [定义了 20 种](https://github.com/facebook/react/blob/v17.0.2/packages/shared/ReactSymbols.js#L16-L37)内部节点类型. 根据运行时环境不同, 分别采用 16 进制的字面量和`Symbol`进行表示.\n\n### [ReactComponent](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactBaseClasses.js#L20-L30)对象\n\n对于`ReactElement`来讲, `ReactComponent`仅仅是诸多`type`类型中的一种.\n\n对于开发者来讲, `ReactComponent`使用非常高频(在状态组件章节中详细解读), 在本节只是先证明它只是一种特殊的`ReactElement`.\n\n这里用一个简单的示例, 通过查看编译后的代码来说明\n\n```js\nclass App extends React.Component {\n  render() {\n    return (\n      <div className=\"app\">\n        <header>header</header>\n        <Content />\n        <footer>footer</footer>\n      </div>\n    );\n  }\n}\n\nclass Content extends React.Component {\n  render() {\n    return (\n      <React.Fragment>\n        <p>1</p>\n        <p>2</p>\n        <p>3</p>\n      </React.Fragment>\n    );\n  }\n}\n\nexport default App;\n```\n\n编译之后的代码(此处只编译了 jsx 语法, 并没有将 class 语法编译成 es5 中的 function), 可以更直观的看出调用逻辑.\n\n`createElement`函数的第一个参数将作为创建`ReactElement`的`type`. 可以看到`Content`这个变量被编译器命名为`App_Content`, 并作为第一个参数(引用传递), 传入了`createElement`.\n\n```js\nclass App_App extends react_default.a.Component {\n  render() {\n    return /*#__PURE__*/ react_default.a.createElement(\n      'div',\n      {\n        className: 'app',\n      } /*#__PURE__*/,\n      react_default.a.createElement('header', null, 'header') /*#__PURE__*/,\n\n      // 此处直接将Content传入, 是一个指针传递\n      react_default.a.createElement(App_Content, null) /*#__PURE__*/,\n      react_default.a.createElement('footer', null, 'footer'),\n    );\n  }\n}\nclass App_Content extends react_default.a.Component {\n  render() {\n    return /*#__PURE__*/ react_default.a.createElement(\n      react_default.a.Fragment,\n      null /*#__PURE__*/,\n      react_default.a.createElement('p', null, '1'),\n      /*#__PURE__*/\n\n      react_default.a.createElement('p', null, '2'),\n      /*#__PURE__*/\n\n      react_default.a.createElement('p', null, '3'),\n    );\n  }\n}\n```\n\n上述示例演示了`ReactComponent`是诸多`ReactElement`种类中的一种情况, 但是由于`ReactComponent`是 class 类型, 自有它的特殊性(可[对照源码](https://github.com/facebook/react/blob/v17.0.2/packages/react/src/ReactBaseClasses.js), 更容易理解).\n\n1. `ReactComponent`是 class 类型, 继承父类`Component`, 拥有特殊的方法(`setState`,`forceUpdate`)和特殊的属性(`context`,`updater`等).\n2. 在`reconciler`阶段, 会依据`ReactElement`对象的特征, 生成对应的 fiber 节点. 当识别到`ReactElement`对象是 class 类型的时候, 会触发`ReactComponent`对象的生命周期, 并调用其 `render`方法, 生成`ReactElement`子节点.\n\n### 其他`ReactElement`\n\n上文介绍了第一种特殊的`ReactElement`(`class`类型的组件), 除此之外`function`类型的组件也需要深入了解, 因为`Hook`只能在`function`类型的组件中使用.\n\n如果在`function`类型的组件中没有使用`Hook`(如: `useState`, `useEffect`等), 在`reconciler`阶段所有有关`Hook`的处理都会略过, 最后调用该`function`拿到子节点`ReactElement`.\n\n如果使用了`Hook`, 逻辑就相对复杂, 涉及到`Hook`创建和状态保存(有关 Hook 的原理部分, 在 Hook 原理章节中详细解读). 此处只需要了解`function`类型的组件和`class`类型的组件一样, 是诸多`ReactElement`形式中的一种.\n\n### `ReactElement`内存结构\n\n通过前文对`ReactElement`的介绍, 可以比较容易的画出`<App/>`这个`ReactElement`对象在内存中的结构(`reconciler`阶段完成之后才会形成完整的结构).\n\n<img src=\"../../snapshots/data-structure/reactelement-tree.png\" width=\"600\">\n\n注意:\n\n- `class`和`function`类型的组件,其子节点是在 render 之后(`reconciler`阶段)才生成的. 此处只是单独表示`ReactElement`的数据结构.\n- 父级对象和子级对象之间是通过`props.children`属性进行关联的(与 fiber 树不同).\n- `ReactElement`虽然不能算是一个严格的树, 也不能算是一个严格的链表. 它的生成过程是自顶向下的, 是所有组件节点的总和.\n- `ReactElement`树(暂且用树来表述)和`fiber`树是以`props.children`为单位`先后交替`生成的(在 fiber 树构建章节详细解读), 当`ReactElement`树构造完毕, fiber 树也随后构造完毕.\n- `reconciler`阶段会根据`ReactElement`的类型生成对应的`fiber`节点(不是一一对应, 比如`Fragment`类型的组件在生成`fiber`节点的时候会略过).\n\n## `react-reconciler` 包\n\n在[宏观结构](./macro-structure.md)中介绍过, `react-reconciler`包是`react`应用的中枢, 连接渲染器(`react-dom`)和调度中心(`scheduler`), 同时自身也负责 fiber 树的构造.\n\n对于此包的深入分析, 放在`fiber 树构建`, `reconciler 工作空间`等章节中.\n\n此处先要知道`fiber`是核心, react 体系的渲染和更新都要以 fiber 作为数据模型, 如果不能理解 fiber, 也无法深入理解 react.\n\n本章先预览一下此包中与`fiber`对象关联度较高的对象.\n\n### Fiber 对象\n\n先看数据结构, 其 type 类型的定义在[`ReactInternalTypes.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactInternalTypes.js#L47-L174)中:\n\n```js\n// 一个Fiber对象代表一个即将渲染或者已经渲染的组件(ReactElement), 一个组件可能对应两个fiber(current和WorkInProgress)\n// 单个属性的解释在后文(在注释中无法添加超链接)\nexport type Fiber = {|\n  tag: WorkTag,\n  key: null | string,\n  elementType: any,\n  type: any,\n  stateNode: any,\n  return: Fiber | null,\n  child: Fiber | null,\n  sibling: Fiber | null,\n  index: number,\n  ref:\n    | null\n    | (((handle: mixed) => void) & { _stringRef: ?string, ... })\n    | RefObject,\n  pendingProps: any, // 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动\n  memoizedProps: any, // 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中\n  updateQueue: mixed, // 存储state更新的队列, 当前节点的state改动之后, 都会创建一个update对象添加到这个队列中.\n  memoizedState: any, // 用于输出的state, 最终渲染所使用的state\n  dependencies: Dependencies | null, // 该fiber节点所依赖的(contexts, events)等\n  mode: TypeOfMode, // 二进制位Bitfield,继承至父节点,影响本fiber节点及其子树中所有节点. 与react应用的运行模式有关(有ConcurrentMode, BlockingMode, NoMode等选项).\n\n  // Effect 副作用相关\n  flags: Flags, // 标志位\n  subtreeFlags: Flags, //替代16.x版本中的 firstEffect, nextEffect. 当设置了 enableNewReconciler=true才会启用\n  deletions: Array<Fiber> | null, // 存储将要被删除的子节点. 当设置了 enableNewReconciler=true才会启用\n\n  nextEffect: Fiber | null, // 单向链表, 指向下一个有副作用的fiber节点\n  firstEffect: Fiber | null, // 指向副作用链表中的第一个fiber节点\n  lastEffect: Fiber | null, // 指向副作用链表中的最后一个fiber节点\n\n  // 优先级相关\n  lanes: Lanes, // 本fiber节点的优先级\n  childLanes: Lanes, // 子节点的优先级\n  alternate: Fiber | null, // 指向内存中的另一个fiber, 每个被更新过fiber节点在内存中都是成对出现(current和workInProgress)\n\n  // 性能统计相关(开启enableProfilerTimer后才会统计)\n  // react-dev-tool会根据这些时间统计来评估性能\n  actualDuration?: number, // 本次更新过程, 本节点以及子树所消耗的总时间\n  actualStartTime?: number, // 标记本fiber节点开始构建的时间\n  selfBaseDuration?: number, // 用于最近一次生成本fiber节点所消耗的时间\n  treeBaseDuration?: number, // 生成子树所消耗的时间的总和\n|};\n```\n\n属性解释:\n\n- `fiber.tag`: 表示 fiber 类型, 根据`ReactElement`组件的 type 进行生成, 在 react 内部共定义了[25 种 tag](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactWorkTags.js#L10-L35).\n- `fiber.key`: 和`ReactElement`组件的 key 一致.\n- `fiber.elementType`: 一般来讲和`ReactElement`组件的 type 一致\n- `fiber.type`: 一般来讲和`fiber.elementType`一致. 一些特殊情形下, 比如在开发环境下为了兼容热更新(`HotReloading`), 会对`function, class, ForwardRef`类型的`ReactElement`做一定的处理, 这种情况会区别于`fiber.elementType`, 具体赋值关系可以查看[源文件](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiber.old.js#L571-L574).\n- `fiber.stateNode`: 与`fiber`关联的局部状态节点(比如: `HostComponent`类型指向与`fiber`节点对应的 dom 节点; 根节点`fiber.stateNode`指向的是`FiberRoot`; class 类型节点其`stateNode`指向的是 class 实例).\n- `fiber.return`: 指向父节点.\n- `fiber.child`: 指向第一个子节点.\n- `fiber.sibling`: 指向下一个兄弟节点.\n- `fiber.index`: fiber 在兄弟节点中的索引, 如果是单节点默认为 0.\n- `fiber.ref`: 指向在`ReactElement`组件上设置的 ref(`string`类型的`ref`除外, 这种类型的`ref`已经不推荐使用, `reconciler`阶段会将`string`类型的`ref`转换成一个`function`类型).\n- `fiber.pendingProps`: 输入属性, 从`ReactElement`对象传入的 props. 用于和`fiber.memoizedProps`比较可以得出属性是否变动.\n- `fiber.memoizedProps`: 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中. 向下生成子节点之前叫做`pendingProps`, 生成子节点之后会把`pendingProps`赋值给`memoizedProps`用于下一次比较.`pendingProps`和`memoizedProps`比较可以得出属性是否变动.\n- `fiber.updateQueue`: 存储`update更新对象`的队列, 每一次发起更新, 都需要在该队列上创建一个`update对象`.\n- `fiber.memoizedState`: 上一次生成子节点之后保持在内存中的局部状态.\n- `fiber.dependencies`: 该 fiber 节点所依赖的(contexts, events)等, 在`context`机制章节详细说明.\n- `fiber.mode`: 二进制位 Bitfield,继承至父节点,影响本 fiber 节点及其子树中所有节点. 与 react 应用的运行模式有关(有 ConcurrentMode, BlockingMode, NoMode 等选项).\n- `fiber.flags`: 标志位, 副作用标记(在 16.x 版本中叫做`effectTag`, 相应[pr](https://github.com/facebook/react/pull/19755)), 在[`ReactFiberFlags.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberFlags.js#L10-L41)中定义了所有的标志位. `reconciler`阶段会将所有拥有`flags`标记的节点添加到副作用链表中, 等待 commit 阶段的处理.\n- `fiber.subtreeFlags`: 替代 16.x 版本中的 firstEffect, nextEffect. 默认未开启, 当设置了[enableNewReconciler=true](https://github.com/facebook/react/blob/v17.0.2/packages/shared/ReactFeatureFlags.js#L93) 才会启用, 本系列只跟踪稳定版的代码, 未来版本不会深入解读, [使用示例见源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCompleteWork.new.js#L690-L714).\n- `fiber.deletions`: 存储将要被删除的子节点. 默认未开启, 当设置了[enableNewReconciler=true](https://github.com/facebook/react/blob/v17.0.2/packages/shared/ReactFeatureFlags.js#L93) 才会启用, 本系列只跟踪稳定版的代码, 未来版本不会深入解读, [使用示例见源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactChildFiber.new.js#L275-L287).\n- `fiber.nextEffect`: 单向链表, 指向下一个有副作用的 fiber 节点.\n- `fiber.firstEffect`: 指向副作用链表中的第一个 fiber 节点.\n- `fiber.lastEffect`: 指向副作用链表中的最后一个 fiber 节点.\n- `fiber.lanes`: 本 fiber 节点所属的优先级, 创建 fiber 的时候设置.\n- `fiber.childLanes`: 子节点所属的优先级.\n- `fiber.alternate`: 指向内存中的另一个 fiber, 每个被更新过 fiber 节点在内存中都是成对出现(current 和 workInProgress)\n\n通过以上 25 个属性的解释, 对`fiber`对象有一个初步的认识.\n\n最后绘制一颗 fiber 树与上文中的`ReactElement`树对照起来:\n\n<div>\n<img src=\"../../snapshots/data-structure/reactelement-tree.png\" alt=\"reactelement\" width=\"500\">\n<img src=\"../../snapshots/data-structure/fiber-tree.png\" alt=\"fiber\"  width=\"500\">\n</div>\n注意:\n\n- 这里的`fiber`树只是为了和上文中的`ReactElement`树对照, 所以只用观察红色虚线框内的节点. 根节点`HostRootFiber`在[react 应用的启动模式章节中](./bootstrap.md)详细解读.\n- 其中`<App/>`,`<Content/>`为`ClassComponent`类型的`fiber`节点, 其余节点都是普通`HostComponent`类型节点.\n- `<Content/>`的子节点在`ReactElement`树中是`React.Fragment`, 但是在`fiber`树中`React.Fragment`并没有与之对应的`fiber`节点(`reconciler`阶段对此类型节点做了单独处理, 所以`ReactElement`节点和`fiber`节点不是一对一匹配).\n\n### Update 与 UpdateQueue 对象\n\n在`fiber`对象中有一个属性`fiber.updateQueue`, 是一个链式队列(即使用链表实现的队列存储结构), 后文会根据场景表述成链表或队列.\n\n首先观察`Update`对象的数据结构([对照源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactUpdateQueue.old.js#L106-L129)):\n\n```js\nexport type Update<State> = {|\n  eventTime: number, // 发起update事件的时间(17.0.2中作为临时字段, 即将移出)\n  lane: Lane, // update所属的优先级\n\n  tag: 0 | 1 | 2 | 3, //\n  payload: any, // 载荷, 根据场景可以设置成一个回调函数或者对象\n  callback: (() => mixed) | null, // 回调函数\n\n  next: Update<State> | null, // 指向链表中的下一个, 由于UpdateQueue是一个环形链表, 最后一个update.next指向第一个update对象\n|};\n\n// =============== UpdateQueue ==============\ntype SharedQueue<State> = {|\n  pending: Update<State> | null,\n|};\n\nexport type UpdateQueue<State> = {|\n  baseState: State,\n  firstBaseUpdate: Update<State> | null,\n  lastBaseUpdate: Update<State> | null,\n  shared: SharedQueue<State>,\n  effects: Array<Update<State>> | null,\n|};\n```\n\n属性解释:\n\n1. `UpdateQueue`\n\n   - `baseState`: 表示此队列的基础 state\n   - `firstBaseUpdate`: 指向基础队列的队首\n   - `lastBaseUpdate`: 指向基础队列的队尾\n   - `shared`: 共享队列\n   - `effects`: 用于保存有`callback`回调函数的 update 对象, 在`commit`之后, 会依次调用这里的回调函数.\n\n2. `SharedQueue`\n\n   - `pending`: 指向即将输入的`update`队列. 在`class`组件中调用`setState()`之后, 会将新的 update 对象添加到这个队列中来.\n\n3. `Update`\n   - `eventTime`: 发起`update`事件的时间(17.0.2 中作为临时字段, 即将移出)\n   - `lane`: `update`所属的优先级\n   - `tag`: 表示`update`种类, 共 4 种. [`UpdateState,ReplaceState,ForceUpdate,CaptureUpdate`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactUpdateQueue.old.js#L131-L134)\n   - `payload`: 载荷, `update`对象真正需要更新的数据, 可以设置成一个回调函数或者对象.\n   - `callback`: 回调函数. `commit`完成之后会调用.\n   - `next`: 指向链表中的下一个, 由于`UpdateQueue`是一个环形链表, 最后一个`update.next`指向第一个`update`对象.\n\n`updateQueue`是`fiber`对象的一个属性, 所以不能脱离`fiber`存在. 它们之间数据结构和引用关系如下:\n\n![](../../snapshots/data-structure/updatequeue.png)\n\n注意:\n\n- 此处只是展示数据结构和引用关系.对于`updateQueue`在更新阶段的实际作用和运行逻辑, 会在状态组件(class 与 function)章节中详细解读.\n\n### Hook 对象\n\n`Hook`用于`function`组件中, 能够保持`function`组件的状态(与`class`组件中的`state`在性质上是相同的, 都是为了保持组件的状态).在`react@16.8`以后, 官方开始推荐使用`Hook`语法, 常用的 api 有`useState`,`useEffect`,`useCallback`等, 官方一共定义了[14 种`Hook`类型](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L111-L125).\n\n这些 api 背后都会创建一个`Hook`对象, 先观察[`Hook`对象的数据结构](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L134-L140):\n\n```js\nexport type Hook = {|\n  memoizedState: any,\n  baseState: any,\n  baseQueue: Update<any, any> | null,\n  queue: UpdateQueue<any, any> | null,\n  next: Hook | null,\n|};\n\ntype Update<S, A> = {|\n  lane: Lane,\n  action: A,\n  eagerReducer: ((S, A) => S) | null,\n  eagerState: S | null,\n  next: Update<S, A>,\n  priority?: ReactPriorityLevel,\n|};\n\ntype UpdateQueue<S, A> = {|\n  pending: Update<S, A> | null,\n  dispatch: ((A) => mixed) | null,\n  lastRenderedReducer: ((S, A) => S) | null,\n  lastRenderedState: S | null,\n|};\n```\n\n属性解释:\n\n1. `Hook`\n\n- `memoizedState`: 内存状态, 用于输出成最终的`fiber`树\n- `baseState`: 基础状态, 当`Hook.queue`更新过后, `baseState`也会更新.\n- `baseQueue`: 基础状态队列, 在`reconciler`阶段会辅助状态合并.\n- `queue`: 指向一个`Update`队列\n- `next`: 指向该`function`组件的下一个`Hook`对象, 使得多个`Hook`之间也构成了一个链表.\n\n2. `Hook.queue`和 `Hook.baseQueue`(即`UpdateQueue`和`Update`）是为了保证`Hook`对象能够顺利更新, 与上文`fiber.updateQueue`中的`UpdateQueue和Update`是不一样的(且它们在不同的文件), 其逻辑会在状态组件(class 与 function)章节中详细解读.\n\n`Hook`与`fiber`的关系:\n\n在`fiber`对象中有一个属性`fiber.memoizedState`指向`fiber`节点的内存状态. 在`function`类型的组件中, `fiber.memoizedState`就指向`Hook`队列(`Hook`队列保存了`function`类型的组件状态).\n\n所以`Hook`也不能脱离`fiber`而存在, 它们之间的引用关系如下:\n\n![](../../snapshots/data-structure/fiber-hook.png)\n\n注意:\n\n- 此处只是展示数据结构和引用关系.对于`Hook`在运行时的实际作用和逻辑, 会在状态组件(class 与 function)章节中详细解读.\n\n## scheduler 包\n\n如[宏观结构](./macro-structure.md)中所介绍, `scheduler`包负责调度, 在内部维护一个任务队列([taskQueue](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js#L63)). 这个队列是一个最小堆数组(详见[React 算法之堆排序](../algorithm/heapsort.md)), 其中存储了 task 对象.\n\n### Task 对象\n\n`scheduler`包中, 没有为 task 对象定义 type, 其[定义是直接在 js 代码](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js#L316-L326)中:\n\n```js\nvar newTask = {\n  id: taskIdCounter++,\n  callback,\n  priorityLevel,\n  startTime,\n  expirationTime,\n  sortIndex: -1,\n};\n```\n\n属性解释:\n\n- `id`: 唯一标识\n- `callback`: task 最核心的字段, 指向`react-reconciler`包所提供的回调函数.\n- `priorityLevel`: 优先级\n- `startTime`: 一个时间戳,代表 task 的开始时间(创建时间 + 延时时间).\n- `expirationTime`: 过期时间.\n- `sortIndex`: 控制 task 在队列中的次序, 值越小的越靠前.\n\n注意`task`中没有`next`属性, 它不是一个链表, 其顺序是通过堆排序来实现的(小顶堆数组, 始终保证数组中的第一个`task`对象优先级最高).\n\n![](../../snapshots/data-structure/taskqueue.png)\n\n## 总结\n\n本章主要浏览了 react 运行链路中出现的高频对象, 并对它们的数据结构做出了单独解释. 提前了解这些对象的数据结构, 更加有利于之后对 react 源码的深入分析. 在后续对整个运行核心的解读中会多次引用到这些对象, 并对其在运行时的具体作用深入解读.\n"
  },
  {
    "path": "docs/main/priority.md",
    "content": "---\ntitle: 优先级管理\ngroup: 运行核心\norder: 2\n---\n\n# React 中的优先级管理\n\n> `React`内部对于`优先级`的管理, 根据功能的不同分为`LanePriority`, `SchedulerPriority`, `ReactPriorityLevel`3 种类型. 本文基于`react@17.0.2`, 梳理源码中的优先级管理体系.\n\n`React`是一个声明式, 高效且灵活的用于构建用户界面的 JavaScript 库. React 团队一直致力于实现高效渲染, 其中有 2 个十分有名的演讲:\n\n1. [2017 年 Lin Clark 的演讲](http://conf2017.reactjs.org/speakers/lin)中介绍了`fiber`架构和`可中断渲染`.\n2. [2018 年 Dan 在 JSConf 冰岛的演讲](https://zh-hans.reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html)进一步介绍了时间切片(`time slicing`)和异步渲染(`suspense`)等特性.\n\n演讲中所展示的`可中断渲染`,`时间切片(time slicing)`,`异步渲染(suspense)`等特性, 在源码中得以实现都依赖于`优先级管理`.\n\n在`React@17.0.2`源码中, 一共有`2套优先级体系`和`1套转换体系`, 在深入分析之前, 再次回顾一下([reconciler 运作流程](./reconciler-workflow.md)):\n\n![](../../snapshots/reconciler-workflow/reactfiberworkloop.png)\n\n`React`内部对于`优先级`的管理, 贯穿运作流程的 4 个阶段(从输入到输出), 根据其功能的不同, 可以分为 3 种类型:\n\n1. `fiber`优先级(`LanePriority`): 位于`react-reconciler`包, 也就是[`Lane(车道模型)`](https://github.com/facebook/react/pull/18796).\n2. 调度优先级(`SchedulerPriority`): 位于`scheduler`包.\n3. 优先级等级(`ReactPriorityLevel`) : 位于`react-reconciler`包中的[`SchedulerWithReactIntegration.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js), 负责上述 2 套优先级体系的转换.\n\n## 预备知识\n\n在深入分析 3 种优先级之前, 为了深入理解`LanePriority`, 需要先了解`Lane`, 这是`react@17.0.0`的新特性.\n\n### Lane (车道模型)\n\n> 英文单词`lane`翻译成中文表示\"车道, 航道\"的意思, 所以很多文章都将`Lanes`模型称为`车道模型`\n\n`Lane`模型的源码在[ReactFiberLane.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js), 源码中大量使用了位运算(有关位运算的讲解, 可以参考[React 算法之位运算](../algorithm/bitfield.md)).\n\n首先引入作者对`Lane`的解释([相应的 pr](https://github.com/facebook/react/pull/18796)), 这里简单概括如下:\n\n1. `Lane`类型被定义为二进制变量, 利用了位掩码的特性, 在频繁运算的时候占用内存少, 计算速度快.\n   - `Lane`和`Lanes`就是单数和复数的关系, 代表单个任务的定义为`Lane`, 代表多个任务的定义为`Lanes`\n2. `Lane`是对于`expirationTime`的重构, 以前使用`expirationTime`表示的字段, 都改为了`lane`\n\n   ```js\n     renderExpirationtime -> renderLanes\n     update.expirationTime -> update.lane\n     fiber.expirationTime -> fiber.lanes\n     fiber.childExpirationTime -> fiber.childLanes\n     root.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes\n   ```\n\n3. 使用`Lanes`模型相比`expirationTime`模型的优势:\n\n   1. `Lanes`把任务优先级从批量任务中分离出来, 可以更方便的判断单个任务与批量任务的优先级是否重叠.\n\n      ```js\n      // 判断: 单task与batchTask的优先级是否重叠\n      //1. 通过expirationTime判断\n      const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;\n      //2. 通过Lanes判断\n      const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;\n\n      // 当同时处理一组任务, 该组内有多个任务, 且每个任务的优先级不一致\n      // 1. 如果通过expirationTime判断. 需要维护一个范围(在Lane重构之前, 源码中就是这样比较的)\n      const isTaskIncludedInBatch =\n        taskPriority <= highestPriorityInRange &&\n        taskPriority >= lowestPriorityInRange;\n      //2. 通过Lanes判断\n      const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;\n      ```\n\n   2. `Lanes`使用单个 32 位二进制变量即可代表多个不同的任务, 也就是说一个变量即可代表一个组(`group`), 如果要在一个 group 中分离出单个 task, 非常容易.\n\n      > 在`expirationTime`模型设计之初, react 体系中还没有[Suspense 异步渲染](https://zh-hans.reactjs.org/docs/concurrent-mode-suspense.html)的概念.\n      > 现在有如下场景: 有 3 个任务, 其优先级 `A > B > C`, 正常来讲只需要按照优先级顺序执行就可以了.\n      > 但是现在情况变了: A 和 C 任务是`CPU密集型`, 而 B 是`IO密集型`(Suspense 会调用远程 api, 算是 IO 任务), 即 `A(cpu) > B(IO) > C(cpu)`. 此时的需求需要将任务`B`从 group 中分离出来, 先处理 cpu 任务`A和C`.\n\n      ```js\n      // 从group中删除或增加task\n\n      //1. 通过expirationTime实现\n      // 0) 维护一个链表, 按照单个task的优先级顺序进行插入\n      // 1) 删除单个task(从链表中删除一个元素)\n      task.prev.next = task.next;\n      // 2) 增加单个task(需要对比当前task的优先级, 插入到链表正确的位置上)\n      let current = queue;\n      while (task.expirationTime >= current.expirationTime) {\n        current = current.next;\n      }\n      task.next = current.next;\n      current.next = task;\n      // 3) 比较task是否在group中\n      const isTaskIncludedInBatch =\n        taskPriority <= highestPriorityInRange &&\n        taskPriority >= lowestPriorityInRange;\n\n      // 2. 通过Lanes实现\n      // 1) 删除单个task\n      batchOfTasks &= ~task;\n      // 2) 增加单个task\n      batchOfTasks |= task;\n      // 3) 比较task是否在group中\n      const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;\n      ```\n\n      通过上述伪代码, 可以看到`Lanes`的优越性, 运用起来代码量少, 简洁高效.\n\n4. `Lanes`是一个不透明的类型, 只能在[`ReactFiberLane.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js)这个模块中维护. 如果要在其他文件中使用, 只能通过`ReactFiberLane.js`中提供的工具函数来使用.\n\n分析车道模型的源码([`ReactFiberLane.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js)中), 可以得到如下结论:\n\n1. 可以使用的比特位一共有 31 位(为什么? 可以参考[React 算法之位运算](../algorithm/bitfield.md)中的说明).\n2. 共定义了[18 种车道(`Lane/Lanes`)变量](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L74-L103), 每一个变量占有 1 个或多个比特位, 分别定义为`Lane`和`Lanes`类型.\n3. 每一种车道(`Lane/Lanes`)都有对应的优先级, 所以源码中定义了 18 种优先级([LanePriority](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L12-L30)).\n4. 占有低位比特位的`Lane`变量对应的优先级越高\n   - 最高优先级为`SyncLanePriority`对应的车道为`SyncLane = 0b0000000000000000000000000000001`.\n   - 最低优先级为`OffscreenLanePriority`对应的车道为`OffscreenLane = 0b1000000000000000000000000000000`.\n\n## 优先级区别和联系\n\n在源码中, 3 种优先级位于不同的 js 文件, 是相互独立的.\n\n注意:\n\n- `LanePriority`和`SchedulerPriority`从命名上看, 它们代表的是`优先级`\n- `ReactPriorityLevel`从命名上看, 它代表的是`等级`而不是优先级, 它用于衡量`LanePriority`和`SchedulerPriority`的等级.\n\n### LanePriority\n\n`LanePriority`: 属于`react-reconciler`包, 定义于`ReactFiberLane.js`([见源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L46-L70)).\n\n```js\nexport const SyncLanePriority: LanePriority = 15;\nexport const SyncBatchedLanePriority: LanePriority = 14;\n\nconst InputDiscreteHydrationLanePriority: LanePriority = 13;\nexport const InputDiscreteLanePriority: LanePriority = 12;\n\n// .....\n\nconst OffscreenLanePriority: LanePriority = 1;\nexport const NoLanePriority: LanePriority = 0;\n```\n\n与`fiber`构造过程相关的优先级(如`fiber.updateQueue`,`fiber.lanes`)都使用`LanePriority`.\n\n由于本节重点介绍优先级体系以及它们的转换关系, 关于`Lane(车道模型)`在`fiber树构造`时的具体使用, 在`fiber 树构造`章节详细解读.\n\n### SchedulerPriority\n\n`SchedulerPriority`, 属于`scheduler`包, 定义于`SchedulerPriorities.js`中([见源码](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/SchedulerPriorities.js)).\n\n```js\nexport const NoPriority = 0;\nexport const ImmediatePriority = 1;\nexport const UserBlockingPriority = 2;\nexport const NormalPriority = 3;\nexport const LowPriority = 4;\nexport const IdlePriority = 5;\n```\n\n与`scheduler`调度中心相关的优先级使用`SchedulerPriority`.\n\n### ReactPriorityLevel\n\n`reactPriorityLevel`, 属于`react-reconciler`包, 定义于`SchedulerWithReactIntegration.js`中([见源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js#L65-L71)).\n\n```js\nexport const ImmediatePriority: ReactPriorityLevel = 99;\nexport const UserBlockingPriority: ReactPriorityLevel = 98;\nexport const NormalPriority: ReactPriorityLevel = 97;\nexport const LowPriority: ReactPriorityLevel = 96;\nexport const IdlePriority: ReactPriorityLevel = 95;\n// NoPriority is the absence of priority. Also React-only.\nexport const NoPriority: ReactPriorityLevel = 90;\n```\n\n`LanePriority`与`SchedulerPriority`通过`ReactPriorityLevel`进行转换\n\n### 转换关系\n\n为了能协同调度中心(`scheduler`包)和 fiber 树构造(`react-reconciler`包)中对优先级的使用, 则需要转换`SchedulerPriority`和`LanePriority`, 转换的桥梁正是`ReactPriorityLevel`.\n\n在[`SchedulerWithReactIntegration.js`中](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/SchedulerWithReactIntegration.old.js#L93-L125), 可以互转`SchedulerPriority` 和 `ReactPriorityLevel`:\n\n```js\n// 把 SchedulerPriority 转换成 ReactPriorityLevel\nexport function getCurrentPriorityLevel(): ReactPriorityLevel {\n  switch (Scheduler_getCurrentPriorityLevel()) {\n    case Scheduler_ImmediatePriority:\n      return ImmediatePriority;\n    case Scheduler_UserBlockingPriority:\n      return UserBlockingPriority;\n    case Scheduler_NormalPriority:\n      return NormalPriority;\n    case Scheduler_LowPriority:\n      return LowPriority;\n    case Scheduler_IdlePriority:\n      return IdlePriority;\n    default:\n      invariant(false, 'Unknown priority level.');\n  }\n}\n\n// 把 ReactPriorityLevel 转换成 SchedulerPriority\nfunction reactPriorityToSchedulerPriority(reactPriorityLevel) {\n  switch (reactPriorityLevel) {\n    case ImmediatePriority:\n      return Scheduler_ImmediatePriority;\n    case UserBlockingPriority:\n      return Scheduler_UserBlockingPriority;\n    case NormalPriority:\n      return Scheduler_NormalPriority;\n    case LowPriority:\n      return Scheduler_LowPriority;\n    case IdlePriority:\n      return Scheduler_IdlePriority;\n    default:\n      invariant(false, 'Unknown priority level.');\n  }\n}\n```\n\n在[`ReactFiberLane.js`中](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberLane.js#L196-L247), 可以互转`LanePriority` 和 `ReactPriorityLevel`:\n\n```js\nexport function schedulerPriorityToLanePriority(\n  schedulerPriorityLevel: ReactPriorityLevel,\n): LanePriority {\n  switch (schedulerPriorityLevel) {\n    case ImmediateSchedulerPriority:\n      return SyncLanePriority;\n    // ... 省略部分代码\n    default:\n      return NoLanePriority;\n  }\n}\n\nexport function lanePriorityToSchedulerPriority(\n  lanePriority: LanePriority,\n): ReactPriorityLevel {\n  switch (lanePriority) {\n    case SyncLanePriority:\n    case SyncBatchedLanePriority:\n      return ImmediateSchedulerPriority;\n    // ... 省略部分代码\n    default:\n      invariant(\n        false,\n        'Invalid update priority: %s. This is a bug in React.',\n        lanePriority,\n      );\n  }\n}\n```\n\n## 优先级使用\n\n通过[reconciler 运作流程](./reconciler-workflow.md)中的归纳, `reconciler`从输入到输出一共经历了 4 个阶段, 在每个阶段中都会涉及到与`优先级`相关的处理. 正是通过`优先级`的灵活运用, `React`实现了`可中断渲染`,`时间切片(time slicing)`,`异步渲染(suspense)`等特性.\n\n在理解了优先级的基本思路之后, 接下来就正式进入 react 源码分析中的硬核部分(`scheduler 调度原理`和`fiber树构造`)\n\n## 总结\n\n本文介绍了 react 源码中有关优先级的部分, 并梳理了 3 种优先级之间的区别和联系. 它们贯穿了[reconciler 运作流程](./reconciler-workflow.md)中的 4 个阶段, 在 react 源码中所占用的代码量比较高, 理解它们的设计思路, 为接下来分析`调度原理`和`fiber构造`打下基础.\n"
  },
  {
    "path": "docs/main/reconciler-workflow.md",
    "content": "---\ntitle: reconciler 运作流程\ngroup:\n  title: 运行核心\n  order: 1\norder: 1\n---\n\n# reconciler 运作流程\n\n## 概览\n\n通过前文[宏观包结构](./macro-structure.md)和[两大工作循环](./workloop.md)中的介绍, 对`react-reconciler`包有一定了解.\n\n此处先归纳一下`react-reconciler`包的主要作用, 将主要功能分为 4 个方面:\n\n1. 输入: 暴露`api`函数(如: `scheduleUpdateOnFiber`), 供给其他包(如`react`包)调用.\n2. 注册调度任务: 与调度中心(`scheduler`包)交互, 注册调度任务`task`, 等待任务回调.\n3. 执行任务回调: 在内存中构造出`fiber树`, 同时与与渲染器(`react-dom`)交互, 在内存中创建出与`fiber`对应的`DOM`节点.\n4. 输出: 与渲染器(`react-dom`)交互, 渲染`DOM`节点.\n\n以上功能源码都集中在[ReactFiberWorkLoop.js](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js)中. 现在将这些功能(从输入到输出)串联起来, 用下图表示:\n\n![](../../snapshots/reconciler-workflow/reactfiberworkloop.png)\n\n图中的`1,2,3,4`步骤可以反映`react-reconciler`包`从输入到输出`的运作流程,这是一个固定流程, 每一次更新都会运行.\n\n## 分解\n\n图中只列举了最核心的函数调用关系(其中的每一步都有各自的实现细节, 会在后续的章节中逐一展开). 将上述 4 个步骤逐一分解, 了解它们的主要逻辑.\n\n### 输入\n\n在`ReactFiberWorkLoop.js`中, 承接输入的函数只有`scheduleUpdateOnFiber`[源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619). 在`react-reconciler`对外暴露的 api 函数中, 只要涉及到需要改变 fiber 的操作(无论是`首次渲染`或`后续更新`操作), 最后都会间接调用`scheduleUpdateOnFiber`, 所以`scheduleUpdateOnFiber`函数是输入链路中的`必经之路`.\n\n```js\n// 唯一接收输入信号的函数\nexport function scheduleUpdateOnFiber(\n  fiber: Fiber,\n  lane: Lane,\n  eventTime: number,\n) {\n  // ... 省略部分无关代码\n  const root = markUpdateLaneFromFiberToRoot(fiber, lane);\n  if (lane === SyncLane) {\n    if (\n      (executionContext & LegacyUnbatchedContext) !== NoContext &&\n      (executionContext & (RenderContext | CommitContext)) === NoContext\n    ) {\n      // 直接进行`fiber构造`\n      performSyncWorkOnRoot(root);\n    } else {\n      // 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`\n      ensureRootIsScheduled(root, eventTime);\n    }\n  } else {\n    // 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`\n    ensureRootIsScheduled(root, eventTime);\n  }\n}\n```\n\n逻辑进入到`scheduleUpdateOnFiber`之后, 后面有 2 种可能:\n\n1. 不经过调度, 直接进行`fiber构造`.\n2. 注册调度任务, 经过`Scheduler`包的调度, 间接进行`fiber构造`.\n\n### 注册调度任务\n\n与`输入`环节紧密相连, `scheduleUpdateOnFiber`函数之后, 立即进入`ensureRootIsScheduled`函数([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L674-L736)):\n\n```js\n// ... 省略部分无关代码\nfunction ensureRootIsScheduled(root: FiberRoot, currentTime: number) {\n  // 前半部分: 判断是否需要注册新的调度\n  const existingCallbackNode = root.callbackNode;\n  const nextLanes = getNextLanes(\n    root,\n    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,\n  );\n  const newCallbackPriority = returnNextLanesPriority();\n  if (nextLanes === NoLanes) {\n    return;\n  }\n  if (existingCallbackNode !== null) {\n    const existingCallbackPriority = root.callbackPriority;\n    if (existingCallbackPriority === newCallbackPriority) {\n      return;\n    }\n    cancelCallback(existingCallbackNode);\n  }\n\n  // 后半部分: 注册调度任务\n  let newCallbackNode;\n  if (newCallbackPriority === SyncLanePriority) {\n    newCallbackNode = scheduleSyncCallback(\n      performSyncWorkOnRoot.bind(null, root),\n    );\n  } else if (newCallbackPriority === SyncBatchedLanePriority) {\n    newCallbackNode = scheduleCallback(\n      ImmediateSchedulerPriority,\n      performSyncWorkOnRoot.bind(null, root),\n    );\n  } else {\n    const schedulerPriorityLevel =\n      lanePriorityToSchedulerPriority(newCallbackPriority);\n    newCallbackNode = scheduleCallback(\n      schedulerPriorityLevel,\n      performConcurrentWorkOnRoot.bind(null, root),\n    );\n  }\n  root.callbackPriority = newCallbackPriority;\n  root.callbackNode = newCallbackNode;\n}\n```\n\n`ensureRootIsScheduled`的逻辑很清晰, 分为 2 部分:\n\n1. 前半部分: 判断是否需要注册新的调度(如果无需新的调度, 会退出函数)\n2. 后半部分: 注册调度任务\n   - `performSyncWorkOnRoot`或`performConcurrentWorkOnRoot`被封装到了任务回调(`scheduleCallback`)中\n   - 等待调度中心执行任务, 任务运行其实就是执行`performSyncWorkOnRoot`或`performConcurrentWorkOnRoot`\n\n### 执行任务回调\n\n任务回调, 实际上就是执行`performSyncWorkOnRoot`或`performConcurrentWorkOnRoot`. 简单看一下它们的源码(在`fiber树构造`章节再深入分析), 将主要逻辑剥离出来, 单个函数的代码量并不多.\n\n[performSyncWorkOnRoot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L965-L1045):\n\n```js\n// ... 省略部分无关代码\nfunction performSyncWorkOnRoot(root) {\n  let lanes;\n  let exitStatus;\n\n  lanes = getNextLanes(root, NoLanes);\n  // 1. fiber树构造\n  exitStatus = renderRootSync(root, lanes);\n\n  // 2. 异常处理: 有可能fiber构造过程中出现异常\n  if (root.tag !== LegacyRoot && exitStatus === RootErrored) {\n    // ...\n  }\n\n  // 3. 输出: 渲染fiber树\n  const finishedWork: Fiber = (root.current.alternate: any);\n  root.finishedWork = finishedWork;\n  root.finishedLanes = lanes;\n  commitRoot(root);\n\n  // 退出前再次检测, 是否还有其他更新, 是否需要发起新调度\n  ensureRootIsScheduled(root, now());\n  return null;\n}\n```\n\n`performSyncWorkOnRoot`的逻辑很清晰, 分为 3 部分:\n\n1. fiber 树构造\n2. 异常处理: 有可能 fiber 构造过程中出现异常\n3. 调用输出\n\n[performConcurrentWorkOnRoot](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L740-L839)\n\n```js\n// ... 省略部分无关代码\nfunction performConcurrentWorkOnRoot(root) {\n\n  const originalCallbackNode = root.callbackNode;\n\n  // 1. 刷新pending状态的effects, 有可能某些effect会取消本次任务\n  const didFlushPassiveEffects = flushPassiveEffects();\n  if (didFlushPassiveEffects) {\n    if (root.callbackNode !== originalCallbackNode) {\n      // 任务被取消, 退出调用\n      return null;\n    } else {\n      // Current task was not canceled. Continue.\n    }\n  }\n  // 2. 获取本次渲染的优先级\n  let lanes = getNextLanes(\n    root,\n    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,\n  );\n  // 3. 构造fiber树\n  let exitStatus = renderRootConcurrent(root, lanes);\n\n  if (\n    includesSomeLane(\n      workInProgressRootIncludedLanes,\n      workInProgressRootUpdatedLanes,\n    )\n  ) {\n    // 如果在render过程中产生了新的update, 且新update的优先级与最初render的优先级有交集\n    // 那么最初render无效, 丢弃最初render的结果, 等待下一次调度\n    prepareFreshStack(root, NoLanes);\n  } else if (exitStatus !== RootIncomplete) {\n    // 4. 异常处理: 有可能fiber构造过程中出现异常\n    if (exitStatus === RootErrored) {\n      // ...\n    }.\n    const finishedWork: Fiber = (root.current.alternate: any);\n    root.finishedWork = finishedWork;\n    root.finishedLanes = lanes;\n    // 5. 输出: 渲染fiber树\n    finishConcurrentRender(root, exitStatus, lanes);\n  }\n\n  // 退出前再次检测, 是否还有其他更新, 是否需要发起新调度\n  ensureRootIsScheduled(root, now());\n  if (root.callbackNode === originalCallbackNode) {\n    // 渲染被阻断, 返回一个新的performConcurrentWorkOnRoot函数, 等待下一次调用\n    return performConcurrentWorkOnRoot.bind(null, root);\n  }\n  return null;\n}\n```\n\n`performConcurrentWorkOnRoot`的逻辑与`performSyncWorkOnRoot`的不同之处在于, 对于`可中断渲染`的支持:\n\n1. 调用`performConcurrentWorkOnRoot`函数时, 首先检查是否处于`render`过程中, 是否需要恢复上一次渲染.\n2. 如果本次渲染被中断, 最后返回一个新的 performConcurrentWorkOnRoot 函数, 等待下一次调用.\n\n### 输出\n\n[`commitRoot`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1879-L2254):\n\n```js\n// ... 省略部分无关代码\nfunction commitRootImpl(root, renderPriorityLevel) {\n  // 设置局部变量\n  const finishedWork = root.finishedWork;\n  const lanes = root.finishedLanes;\n\n  // 清空FiberRoot对象上的属性\n  root.finishedWork = null;\n  root.finishedLanes = NoLanes;\n  root.callbackNode = null;\n\n  // 提交阶段\n  let firstEffect = finishedWork.firstEffect;\n  if (firstEffect !== null) {\n    const prevExecutionContext = executionContext;\n    executionContext |= CommitContext;\n    // 阶段1: dom突变之前\n    nextEffect = firstEffect;\n    do {\n      commitBeforeMutationEffects();\n    } while (nextEffect !== null);\n\n    // 阶段2: dom突变, 界面发生改变\n    nextEffect = firstEffect;\n    do {\n      commitMutationEffects(root, renderPriorityLevel);\n    } while (nextEffect !== null);\n    root.current = finishedWork;\n\n    // 阶段3: layout阶段, 调用生命周期componentDidUpdate和回调函数等\n    nextEffect = firstEffect;\n    do {\n      commitLayoutEffects(root, lanes);\n    } while (nextEffect !== null);\n    nextEffect = null;\n    executionContext = prevExecutionContext;\n  }\n  ensureRootIsScheduled(root, now());\n  return null;\n}\n```\n\n在输出阶段,`commitRoot`的实现逻辑是在`commitRootImpl`函数中, 其主要逻辑是处理副作用队列, 将最新的 fiber 树结构反映到 DOM 上.\n\n核心逻辑分为 3 个步骤:\n\n1. `commitBeforeMutationEffects`\n   - dom 变更之前, 主要处理副作用队列中带有`Snapshot`,`Passive`标记的`fiber`节点.\n2. `commitMutationEffects`\n   - dom 变更, 界面得到更新. 主要处理副作用队列中带有`Placement`, `Update`, `Deletion`, `Hydrating`标记的`fiber`节点.\n3. `commitLayoutEffects`\n   - dom 变更后, 主要处理副作用队列中带有`Update | Callback`标记的`fiber`节点.\n\n## 总结\n\n本节从宏观上分析了`reconciler 运作流程`, 并将其分为了 4 个步骤, 基本覆盖了`react-reconciler`包的核心逻辑.\n"
  },
  {
    "path": "docs/main/scheduler.md",
    "content": "---\ntitle: 调度原理\ngroup: 运行核心\norder: 3\n---\n\n# React 调度原理(scheduler)\n\n在 React 运行时中, 调度中心(位于`scheduler`包), 是整个 React 运行时的中枢(其实是心脏), 所以理解`scheduler`调度, 就基本把握了 React 的命门.\n\n在深入分析之前, 建议回顾一下往期与`scheduler`相关的文章(这 3 篇文章不长, 共 10 分钟能浏览完):\n\n- [React 工作循环](./workloop.md): 从宏观的角度介绍 React 体系中两个重要的循环, 其中`任务调度循环`就是本文的主角.\n- [reconciler 运作流程](./reconciler-workflow.md): 从宏观的角度介绍了`react-reconciler`包的核心作用, 并把`reconciler`分为了 4 个阶段. 其中第 2 个阶段`注册调度任务`串联了`scheduler`包和`react-reconciler`包, 其实就是`任务调度循环`中的一个任务(`task`).\n- [React 中的优先级管理](./priority.md): 介绍了 React 体系中的 3 中优先级的管理, 列出了源码中`react-reconciler`与`scheduler`包中关于优先级的转换思路. 其中`SchedulerPriority`控制`任务调度循环`中循环的顺序.\n\n了解上述基础知识之后, 再谈`scheduler`原理, 其实就是在大的框架下去添加实现细节, 相对较为容易. 下面就正式进入主题.\n\n## 调度实现\n\n`调度中心`最核心的代码, 在[SchedulerHostConfig.default.js](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js)中.\n\n### 内核\n\n该 js 文件一共导出了 8 个函数, 最核心的逻辑, 就集中在了这 8 个函数中 :\n\n```js\nexport let requestHostCallback; // 请求及时回调: port.postMessage\nexport let cancelHostCallback; // 取消及时回调: scheduledHostCallback = null\nexport let requestHostTimeout; // 请求延时回调: setTimeout\nexport let cancelHostTimeout; // 取消延时回调: cancelTimeout\nexport let shouldYieldToHost; // 是否让出主线程(currentTime >= deadline && needsPaint): 让浏览器能够执行更高优先级的任务(如ui绘制, 用户输入等)\nexport let requestPaint; // 请求绘制: 设置 needsPaint = true\nexport let getCurrentTime; // 获取当前时间\nexport let forceFrameRate; // 强制设置 yieldInterval (让出主线程的周期). 这个函数虽然存在, 但是从源码来看, 几乎没有用到\n```\n\n我们知道 react 可以在 nodejs 环境中使用, 所以在不同的 js 执行环境中, 这些函数的实现会有区别. 下面基于普通浏览器环境, 对这 8 个函数逐一分析 :\n\n1. 调度相关: 请求或取消调度\n\n- [requestHostCallback](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L224-L230)\n- [cancelHostCallback](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L232-L234)\n- [requestHostTimeout](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L236-L240)\n- [cancelHostTimeout](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L242-L245)\n\n这 4 个函数源码很简洁, 非常好理解, 它们的目的就是请求执行(或取消)回调函数. 现在重点介绍其中的`及时回调`(`延时回调`的 2 个函数暂时属于保留 api, 17.0.2 版本其实没有用上)\n\n```js\n// 接收 MessageChannel 消息\nconst performWorkUntilDeadline = () => {\n  // ...省略无关代码\n  if (scheduledHostCallback !== null) {\n    const currentTime = getCurrentTime();\n    // 更新deadline\n    deadline = currentTime + yieldInterval;\n    // 执行callback\n    scheduledHostCallback(hasTimeRemaining, currentTime);\n  } else {\n    isMessageLoopRunning = false;\n  }\n};\n\nconst channel = new MessageChannel();\nconst port = channel.port2;\nchannel.port1.onmessage = performWorkUntilDeadline;\n\n// 请求回调\nrequestHostCallback = function (callback) {\n  // 1. 保存callback\n  scheduledHostCallback = callback;\n  if (!isMessageLoopRunning) {\n    isMessageLoopRunning = true;\n    // 2. 通过 MessageChannel 发送消息\n    port.postMessage(null);\n  }\n};\n// 取消回调\ncancelHostCallback = function () {\n  scheduledHostCallback = null;\n};\n```\n\n很明显, 请求回调之后`scheduledHostCallback = callback`, 然后通过`MessageChannel`发消息的方式触发`performWorkUntilDeadline`函数, 最后执行回调`scheduledHostCallback`.\n\n此处需要注意: `MessageChannel`在浏览器事件循环中属于`宏任务`, 所以调度中心永远是`异步执行`回调函数.\n\n2. 时间切片(`time slicing`)相关: 执行时间分割, 让出主线程(把控制权归还浏览器, 浏览器可以处理用户输入, UI 绘制等紧急任务).\n\n- [getCurrentTime](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L22-L24): 获取当前时间\n- [shouldYieldToHost](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L129-L152): 是否让出主线程\n- [requestPaint](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L154-L156): 请求绘制\n- [forceFrameRate](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L168-L183): 强制设置 `yieldInterval`(从源码中的引用来看, 算一个保留函数, 其他地方没有用到)\n\n```js\nconst localPerformance = performance;\n// 获取当前时间\ngetCurrentTime = () => localPerformance.now();\n\n// 时间切片周期, 默认是5ms(如果一个task运行超过该周期, 下一个task执行之前, 会把控制权归还浏览器)\nlet yieldInterval = 5;\n\nlet deadline = 0;\nconst maxYieldInterval = 300;\nlet needsPaint = false;\nconst scheduling = navigator.scheduling;\n// 是否让出主线程\nshouldYieldToHost = function () {\n  const currentTime = getCurrentTime();\n  if (currentTime >= deadline) {\n    if (needsPaint || scheduling.isInputPending()) {\n      // There is either a pending paint or a pending input.\n      return true;\n    }\n    // There's no pending input. Only yield if we've reached the max\n    // yield interval.\n    return currentTime >= maxYieldInterval; // 在持续运行的react应用中, currentTime肯定大于300ms, 这个判断只在初始化过程中才有可能返回false\n  } else {\n    // There's still time left in the frame.\n    return false;\n  }\n};\n\n// 请求绘制\nrequestPaint = function () {\n  needsPaint = true;\n};\n\n// 设置时间切片的周期\nforceFrameRate = function (fps) {\n  if (fps < 0 || fps > 125) {\n    // Using console['error'] to evade Babel and ESLint\n    console['error'](\n      'forceFrameRate takes a positive int between 0 and 125, ' +\n        'forcing frame rates higher than 125 fps is not supported',\n    );\n    return;\n  }\n  if (fps > 0) {\n    yieldInterval = Math.floor(1000 / fps);\n  } else {\n    // reset the framerate\n    yieldInterval = 5;\n  }\n};\n```\n\n这 4 个函数代码都很简洁, 其功能在注释中都有解释.\n\n注意`shouldYieldToHost`的判定条件:\n\n- `currentTime >= deadline`: 只有时间超过`deadline`之后才会让出主线程(其中`deadline = currentTime + yieldInterval`).\n  - `yieldInterval`默认是`5ms`, 只能通过`forceFrameRate`函数来修改(事实上在 v17.0.2 源码中, 并没有使用到该函数).\n  - 如果一个`task`运行时间超过`5ms`, 下一个`task`执行之前, 会把控制权归还浏览器.\n- `navigator.scheduling.isInputPending()`: 这 facebook 官方贡献给 Chromium 的 api, 现在已经列入 W3C 标准([具体解释](https://engineering.fb.com/2019/04/22/developer-tools/isinputpending-api/)), 用于判断是否有输入事件(包括: input 框输入事件, 点击事件等).\n\n介绍完这 8 个内部函数, 最后浏览一下完整回调的实现[performWorkUntilDeadline](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/forks/SchedulerHostConfig.default.js#L185-L218)(逻辑很清晰, 在注释中解释):\n\n```js\nconst performWorkUntilDeadline = () => {\n  if (scheduledHostCallback !== null) {\n    const currentTime = getCurrentTime(); // 1. 获取当前时间\n    deadline = currentTime + yieldInterval; // 2. 设置deadline\n    const hasTimeRemaining = true;\n    try {\n      // 3. 执行回调, 返回是否有还有剩余任务\n      const hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);\n      if (!hasMoreWork) {\n        // 没有剩余任务, 退出\n        isMessageLoopRunning = false;\n        scheduledHostCallback = null;\n      } else {\n        port.postMessage(null); // 有剩余任务, 发起新的调度\n      }\n    } catch (error) {\n      port.postMessage(null); // 如有异常, 重新发起调度\n      throw error;\n    }\n  } else {\n    isMessageLoopRunning = false;\n  }\n  needsPaint = false; // 重置开关\n};\n```\n\n分析到这里, 可以得到调度中心的内核实现图:\n\n![](../../snapshots/scheduler/core.png)\n\n说明: 这个流程图很简单, 源码量也很少(总共不到 80 行), 但是它代表了`scheduler`的核心, 所以精华其实并不一定需要很多代码.\n\n### 任务队列管理\n\n通过上文的分析, 我们已经知道请求和取消调度的实现原理. 调度的目的是为了消费任务, 接下来就具体分析任务队列是如何管理与实现的.\n\n在[Scheduler.js](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js)中, 维护了一个[taskQueue](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js#L62), 任务队列管理就是围绕这个`taskQueue`展开.\n\n```js\n// Tasks are stored on a min heap\nvar taskQueue = [];\nvar timerQueue = [];\n```\n\n注意:\n\n- `taskQueue`是一个小顶堆数组, 关于堆排序的详细解释, 可以查看[React 算法之堆排序](../algorithm/heapsort.md).\n- 源码中除了`taskQueue`队列之外还有一个`timerQueue`队列. 这个队列是预留给延时任务使用的, 在 react@17.0.2 版本里面, 从源码中的引用来看, 算一个保留功能, 没有用到.\n\n#### 创建任务\n\n在`unstable_scheduleCallback`函数中([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js#L279-L359)):\n\n```js\n// 省略部分无关代码\nfunction unstable_scheduleCallback(priorityLevel, callback, options) {\n  // 1. 获取当前时间\n  var currentTime = getCurrentTime();\n  var startTime;\n  if (typeof options === 'object' && options !== null) {\n    // 从函数调用关系来看, 在v17.0.2中,所有调用 unstable_scheduleCallback 都未传入options\n    // 所以省略延时任务相关的代码\n  } else {\n    startTime = currentTime;\n  }\n  // 2. 根据传入的优先级, 设置任务的过期时间 expirationTime\n  var timeout;\n  switch (priorityLevel) {\n    case ImmediatePriority:\n      timeout = IMMEDIATE_PRIORITY_TIMEOUT;\n      break;\n    case UserBlockingPriority:\n      timeout = USER_BLOCKING_PRIORITY_TIMEOUT;\n      break;\n    case IdlePriority:\n      timeout = IDLE_PRIORITY_TIMEOUT;\n      break;\n    case LowPriority:\n      timeout = LOW_PRIORITY_TIMEOUT;\n      break;\n    case NormalPriority:\n    default:\n      timeout = NORMAL_PRIORITY_TIMEOUT;\n      break;\n  }\n  var expirationTime = startTime + timeout;\n  // 3. 创建新任务\n  var newTask = {\n    id: taskIdCounter++,\n    callback,\n    priorityLevel,\n    startTime,\n    expirationTime,\n    sortIndex: -1,\n  };\n  if (startTime > currentTime) {\n    // 省略无关代码 v17.0.2中不会使用\n  } else {\n    newTask.sortIndex = expirationTime;\n    // 4. 加入任务队列\n    push(taskQueue, newTask);\n    // 5. 请求调度\n    if (!isHostCallbackScheduled && !isPerformingWork) {\n      isHostCallbackScheduled = true;\n      requestHostCallback(flushWork);\n    }\n  }\n  return newTask;\n}\n```\n\n逻辑很清晰(在注释中已标明), 重点分析`task`对象的各个属性:\n\n```js\nvar newTask = {\n  id: taskIdCounter++, // id: 一个自增编号\n  callback, // callback: 传入的回调函数\n  priorityLevel, // priorityLevel: 优先级等级\n  startTime, // startTime: 创建task时的当前时间\n  expirationTime, // expirationTime: task的过期时间, 优先级越高 expirationTime = startTime + timeout 越小\n  sortIndex: -1,\n};\nnewTask.sortIndex = expirationTime; // sortIndex: 排序索引, 全等于过期时间. 保证过期时间越小, 越紧急的任务排在最前面\n```\n\n#### 消费任务\n\n创建任务之后, 最后请求调度`requestHostCallback(flushWork)`(`创建任务`源码中的第 5 步), `flushWork`函数作为参数被传入调度中心内核等待回调. `requestHostCallback`函数在上文调度内核中已经介绍过了, 在调度中心中, 只需下一个事件循环就会执行回调, 最终执行`flushWork`.\n\n```js\n// 省略无关代码\nfunction flushWork(hasTimeRemaining, initialTime) {\n  // 1. 做好全局标记, 表示现在已经进入调度阶段\n  isHostCallbackScheduled = false;\n  isPerformingWork = true;\n  const previousPriorityLevel = currentPriorityLevel;\n  try {\n    // 2. 循环消费队列\n    return workLoop(hasTimeRemaining, initialTime);\n  } finally {\n    // 3. 还原全局标记\n    currentTask = null;\n    currentPriorityLevel = previousPriorityLevel;\n    isPerformingWork = false;\n  }\n}\n```\n\n`flushWork`中调用了`workLoop`. 队列消费的主要逻辑是在`workLoop`函数中, 这就是[React 工作循环](./workloop.md)一文中提到的`任务调度循环`.\n\n```js\n// 省略部分无关代码\nfunction workLoop(hasTimeRemaining, initialTime) {\n  let currentTime = initialTime; // 保存当前时间, 用于判断任务是否过期\n  currentTask = peek(taskQueue); // 获取队列中的第一个任务\n  while (currentTask !== null) {\n    if (\n      currentTask.expirationTime > currentTime &&\n      (!hasTimeRemaining || shouldYieldToHost())\n    ) {\n      // 虽然currentTask没有过期, 但是执行时间超过了限制(毕竟只有5ms, shouldYieldToHost()返回true). 停止继续执行, 让出主线程\n      break;\n    }\n    const callback = currentTask.callback;\n    if (typeof callback === 'function') {\n      currentTask.callback = null;\n      currentPriorityLevel = currentTask.priorityLevel;\n      const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;\n      // 执行回调\n      const continuationCallback = callback(didUserCallbackTimeout);\n      currentTime = getCurrentTime();\n      // 回调完成, 判断是否还有连续(派生)回调\n      if (typeof continuationCallback === 'function') {\n        // 产生了连续回调(如fiber树太大, 出现了中断渲染), 保留currentTask\n        currentTask.callback = continuationCallback;\n      } else {\n        // 把currentTask移出队列\n        if (currentTask === peek(taskQueue)) {\n          pop(taskQueue);\n        }\n      }\n    } else {\n      // 如果任务被取消(这时currentTask.callback = null), 将其移出队列\n      pop(taskQueue);\n    }\n    // 更新currentTask\n    currentTask = peek(taskQueue);\n  }\n  if (currentTask !== null) {\n    return true; // 如果task队列没有清空, 返回true. 等待调度中心下一次回调\n  } else {\n    return false; // task队列已经清空, 返回false.\n  }\n}\n```\n\n`workLoop`就是一个大循环, 虽然代码也不多, 但是非常精髓, 在此处实现了`时间切片(time slicing)`和`fiber树的可中断渲染`. 这 2 大特性的实现, 都集中于这个`while`循环.\n\n每一次`while`循环的退出就是一个时间切片, 深入分析`while`循环的退出条件:\n\n1. 队列被完全清空: 这种情况就是很正常的情况, 一气呵成, 没有遇到任何阻碍.\n2. 执行超时: 在消费`taskQueue`时, 在执行`task.callback`之前, 都会检测是否超时, 所以超时检测是以`task`为单位.\n   - 如果某个`task.callback`执行时间太长(如: `fiber树`很大, 或逻辑很重)也会造成超时\n   - 所以在执行`task.callback`过程中, 也需要一种机制检测是否超时, 如果超时了就立刻暂停`task.callback`的执行.\n\n#### 时间切片原理\n\n消费任务队列的过程中, 可以消费`1~n`个 task, 甚至清空整个 queue. 但是在每一次具体执行`task.callback`之前都要进行超时检测, 如果超时可以立即退出循环并等待下一次调用.\n\n#### 可中断渲染原理\n\n在时间切片的基础之上, 如果单个`task.callback`执行时间就很长(假设 200ms). 就需要`task.callback`自己能够检测是否超时, 所以在 fiber 树构造过程中, 每构造完成一个单元, 都会检测一次超时([源码链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1637-L1639)), 如遇超时就退出`fiber树构造循环`, 并返回一个新的回调函数(就是此处的`continuationCallback`)并等待下一次回调继续未完成的`fiber树构造`.\n\n## 节流防抖 {#throttle-debounce}\n\n通过上文的分析, 已经覆盖了`scheduler`包中的核心原理. 现在再次回到`react-reconciler`包中, 在调度过程中的关键路径中, 我们还需要理解一些细节.\n\n在[reconciler 运作流程](./reconciler-workflow.md)中总结的 4 个阶段中, `注册调度任务`属于第 2 个阶段, 核心逻辑位于`ensureRootIsScheduled`函数中.\n现在我们已经理解了`调度原理`, 再次分析`ensureRootIsScheduled`([源码地址](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L674-L736)):\n\n```js\n// ... 省略部分无关代码\nfunction ensureRootIsScheduled(root: FiberRoot, currentTime: number) {\n  // 前半部分: 判断是否需要注册新的调度\n  const existingCallbackNode = root.callbackNode;\n  const nextLanes = getNextLanes(\n    root,\n    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,\n  );\n  const newCallbackPriority = returnNextLanesPriority();\n  if (nextLanes === NoLanes) {\n    return;\n  }\n  // 节流防抖\n  if (existingCallbackNode !== null) {\n    const existingCallbackPriority = root.callbackPriority;\n    if (existingCallbackPriority === newCallbackPriority) {\n      return;\n    }\n    cancelCallback(existingCallbackNode);\n  }\n  // 后半部分: 注册调度任务 省略代码...\n\n  // 更新标记\n  root.callbackPriority = newCallbackPriority;\n  root.callbackNode = newCallbackNode;\n}\n```\n\n正常情况下, `ensureRootIsScheduled`函数会与`scheduler`包通信, 最后注册一个`task`并等待回调.\n\n1. 在`task`注册完成之后, 会设置`fiberRoot`对象上的属性(`fiberRoot`是 react 运行时中的重要全局对象, 可参考[React 应用的启动过程](./bootstrap.md#创建全局对象)), 代表现在已经处于调度进行中\n2. 再次进入`ensureRootIsScheduled`时(比如连续 2 次`setState`, 第 2 次`setState`同样会触发`reconciler运作流程`中的调度阶段), 如果发现处于调度中, 则需要一些节流和防抖措施, 进而保证调度性能.\n   1. 节流(判断条件: `existingCallbackPriority === newCallbackPriority`, 新旧更新的优先级相同, 如连续多次执行`setState`), 则无需注册新`task`(继续沿用上一个优先级相同的`task`), 直接退出调用.\n   2. 防抖(判断条件: `existingCallbackPriority !== newCallbackPriority`, 新旧更新的优先级不同), 则取消旧`task`, 重新注册新`task`.\n\n## 总结\n\n本节主要分析了`scheduler`包中`调度原理`, 也就是`React两大工作循环`中的`任务调度循环`. 并介绍了`时间切片`和`可中断渲染`等特性在`任务调度循环`中的实现. `scheduler`包是`React`运行时的心脏, 为了提升调度性能, 注册`task`之前, 在`react-reconciler`包中做了节流和防抖等措施.\n"
  },
  {
    "path": "docs/main/state-effects.md",
    "content": "---\ntitle: 状态与副作用\ngroup:\n  title: 状态管理\n  order: 2\norder: 0\n---\n\n# 状态与副作用\n\n在前文我们已经分析了`fiber树`从`构造`到`渲染`的关键过程. 本节我们站在`fiber`对象的视角, 考虑一个具体的`fiber`节点如何影响最终的渲染.\n\n回顾[fiber 数据结构](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactInternalTypes.js#L47-L174), 并结合前文`fiber树构造`系列的解读, 我们注意到`fiber`众多属性中, 有 2 类属性十分关键:\n\n1. `fiber`节点的自身状态: 在`renderRootSync[Concurrent]`阶段, 为子节点提供确定的输入数据, 直接影响子节点的生成.\n\n2. `fiber`节点的副作用: 在`commitRoot`阶段, 如果`fiber`被标记有副作用, 则副作用相关函数会被(同步/异步)调用.\n\n```js\nexport type Fiber = {|\n  // 1. fiber节点自身状态相关\n  pendingProps: any,\n  memoizedProps: any,\n  updateQueue: mixed,\n  memoizedState: any,\n\n  // 2. fiber节点副作用(Effect)相关\n  flags: Flags,\n  subtreeFlags: Flags, // v17.0.2未启用\n  deletions: Array<Fiber> | null, // v17.0.2未启用\n  nextEffect: Fiber | null,\n  firstEffect: Fiber | null,\n  lastEffect: Fiber | null,\n|};\n```\n\n## 状态\n\n与`状态`相关有 4 个属性:\n\n1. `fiber.pendingProps`: 输入属性, 从`ReactElement`对象传入的 props. 它和`fiber.memoizedProps`比较可以得出属性是否变动.\n2. `fiber.memoizedProps`: 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中. 向下生成子节点之前叫做`pendingProps`, 生成子节点之后会把`pendingProps`赋值给`memoizedProps`用于下一次比较.`pendingProps`和`memoizedProps`比较可以得出属性是否变动.\n3. `fiber.updateQueue`: 存储`update更新对象`的队列, 每一次发起更新, 都需要在该队列上创建一个`update对象`.\n4. `fiber.memoizedState`: 上一次生成子节点之后保持在内存中的局部状态.\n\n它们的作用只局限于`fiber树构造`阶段, 直接影响子节点的生成.\n\n## 副作用\n\n与`副作用`相关有 4 个属性:\n\n1. `fiber.flags`: 标志位, 表明该`fiber`节点有副作用(在 v17.0.2 中共定义了[28 种副作用](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberFlags.js#L13)).\n2. `fiber.nextEffect`: 单向链表, 指向下一个副作用 `fiber`节点.\n3. `fiber.firstEffect`: 单向链表, 指向第一个副作用 `fiber` 节点.\n4. `fiber.lastEffect`: 单向链表, 指向最后一个副作用 `fiber` 节点.\n\n通过前文`fiber树构造`我们知道, 单个`fiber`节点的副作用队列最后都会上移到根节点上. 所以在`commitRoot`阶段中, `react`提供了 3 种处理副作用的方式(详见[fiber 树渲染](./fibertree-commit.md#渲染)).\n\n另外, `副作用`的设计可以理解为对`状态`功能不足的补充.\n\n- `状态`是一个`静态`的功能, 它只能为子节点提供数据源.\n- 而`副作用`是一个`动态`功能, 由于它的调用时机是在`fiber树渲染阶段`, 故它拥有更多的能力, 能轻松获取`突变前快照, 突变后的DOM节点等`. 甚至通过`调用api`发起新的一轮`fiber树构造`, 进而改变更多的`状态`, 引发更多的`副作用`.\n\n## 外部 api\n\n`fiber`对象的这 2 类属性, 可以影响到渲染结果, 但是`fiber`结构始终是一个内核中的结构, 对于外部来讲是无感知的, 对于调用方来讲, 甚至都无需知道`fiber`结构的存在. 所以正常只有通过暴露`api`来直接或间接的修改这 2 类属性.\n\n从`react`包暴露出的`api`来归纳, 只有 2 类组件支持修改:\n\n> 本节只讨论使用`api`的目的是修改`fiber`的`状态`和`副作用`, 进而可以改变整个渲染结果. 本节先介绍 api 与`状态`和`副作用`的联系, 有关`api`的具体实现会在`class组件`,`Hook原理`章节中详细分析.\n\n### class 组件\n\n```js\nclass App extends React.Component {\n  constructor() {\n    this.state = {\n      // 初始状态\n      a: 1,\n    };\n  }\n  changeState = () => {\n    this.setState({ a: ++this.state.a }); // 进入reconciler流程\n  };\n\n  // 生命周期函数: 状态相关\n  static getDerivedStateFromProps(nextProps, prevState) {\n    console.log('getDerivedStateFromProps');\n    return prevState;\n  }\n\n  // 生命周期函数: 状态相关\n  shouldComponentUpdate(newProps, newState, nextContext) {\n    console.log('shouldComponentUpdate');\n    return true;\n  }\n\n  // 生命周期函数: 副作用相关 fiber.flags |= Update\n  componentDidMount() {\n    console.log('componentDidMount');\n  }\n\n  // 生命周期函数: 副作用相关 fiber.flags |= Snapshot\n  getSnapshotBeforeUpdate(prevProps, prevState) {\n    console.log('getSnapshotBeforeUpdate');\n  }\n\n  // 生命周期函数: 副作用相关 fiber.flags |= Update\n  componentDidUpdate() {\n    console.log('componentDidUpdate');\n  }\n\n  render() {\n    // 返回下级ReactElement对象\n    return <button onClick={this.changeState}>{this.state.a}</button>;\n  }\n}\n```\n\n1. 状态相关: `fiber树构造`阶段.\n\n   1. 构造函数: `constructor`实例化时执行, 可以设置初始 state, 只执行一次.\n   2. 生命周期: `getDerivedStateFromProps`在`fiber树构造`阶段(`renderRootSync[Concurrent]`)执行, 可以修改 state([链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L867-L875)).\n   3. 生命周期: `shouldComponentUpdate`在, `fiber树构造`阶段(`renderRootSync[Concurrent]`)执行, 返回值决定是否执行 render([链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberClassComponent.old.js#L1135-L1143)).\n\n2. 副作用相关: `fiber树渲染`阶段.\n   1. 生命周期: `getSnapshotBeforeUpdate`在`fiber树渲染`阶段(`commitRoot->commitBeforeMutationEffects->commitBeforeMutationEffectOnFiber`)执行([链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCommitWork.old.js#L264)).\n   2. 生命周期: `componentDidMount`在`fiber树渲染`阶段(`commitRoot->commitLayoutEffects->commitLayoutEffectOnFiber`)执行([链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCommitWork.old.js#L533)).\n   3. 生命周期: `componentDidUpdate`在`fiber树渲染`阶段(`commitRoot->commitLayoutEffects->commitLayoutEffectOnFiber`)执行([链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCommitWork.old.js#L587)).\n\n可以看到, 官方`api`提供的`class组件`生命周期函数实际上也是围绕`fiber树构造`和`fiber树渲染`来提供的.\n\n### function 组件\n\n注: `function组件`与`class组件`最大的不同是: `class组件`会实例化一个`instance`所以拥有独立的局部状态; 而`function组件`不会实例化, 它只是被直接调用, 故无法维护一份独立的局部状态, 只能依靠`Hook`对象间接实现局部状态(有关更多`Hook`实现细节, 在`Hook原理`章节中详细讨论).\n\n在`v17.0.2`中共定义了[14 种 Hook](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.old.js#L111-L125), 其中最常用的`useState, useEffect, useLayoutEffect等`\n\n```js\nfunction App() {\n  // 状态相关: 初始状态\n  const [a, setA] = useState(1);\n  const changeState = () => {\n    setA(++a); // 进入reconciler流程\n  };\n\n  // 副作用相关: fiber.flags |= Update | Passive;\n  useEffect(() => {\n    console.log(`useEffect`);\n  }, []);\n\n  // 副作用相关: fiber.flags |= Update;\n  useLayoutEffect(() => {\n    console.log(`useLayoutEffect`);\n  }, []);\n\n  // 返回下级ReactElement对象\n  return <button onClick={changeState}>{a}</button>;\n}\n```\n\n1. 状态相关: `fiber树构造`阶段.\n   1. `useState`在`fiber树构造`阶段(`renderRootSync[Concurrent]`)执行, 可以修改`Hook.memoizedState`.\n2. 副作用相关: `fiber树渲染`阶段.\n   1. `useEffect`在`fiber树渲染`阶段(`commitRoot->commitBeforeMutationEffects->commitBeforeMutationEffectOnFiber`)执行(注意是异步执行, [链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L2290-L2295)).\n   2. `useLayoutEffect`在`fiber树渲染`阶段(`commitRoot->commitLayoutEffects->commitLayoutEffectOnFiber->commitHookEffectListMount`)执行(同步执行, [链接](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberCommitWork.old.js#L481)).\n\n### 细节与误区\n\n这里有 2 个细节:\n\n1. `useEffect(function(){}, [])`中的函数是[异步执行](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L2290-L2295), 因为它经过了调度中心(具体实现可以回顾[调度原理](./scheduler.md)).\n2. `useLayoutEffect`和`Class组件`中的`componentDidMount,componentDidUpdate`从调用时机上来讲是等价的, 因为他们都在`commitRoot->commitLayoutEffects`函数中被调用.\n   - 误区: 虽然官网文档推荐尽可能使用标准的 `useEffect` 以避免阻塞视觉更新 , 所以很多开发者使用`useEffect`来代替`componentDidMount,componentDidUpdate`是不准确的, 如果完全类比, `useLayoutEffect`比`useEffect`更符合`componentDidMount,componentDidUpdate`的定义.\n\n为了验证上述结论, 可以查看[codesandbox 中的例子](https://codesandbox.io/s/fervent-napier-1ysb5).\n\n## 总结\n\n本节从`fiber`视角出发, 总结了`fiber`节点中可以影响最终渲染结果的 2 类属性(`状态`和`副作用`).并且归纳了`class`和`function`组件中, 直接或间接更改`fiber`属性的常用方式. 最后从`fiber树构造和渲染`的角度对`class的生命周期函数`与`function的Hooks函数`进行了比较.\n"
  },
  {
    "path": "docs/main/synthetic-event.md",
    "content": "---\ntitle: 合成事件\ngroup:\n  title: 交互\n  order: 3\norder: 0\n---\n\n# React 合成事件\n\n## 概览\n\n从`v17.0.0`开始, React 不会再将事件处理添加到 `document` 上, 而是将事件处理添加到渲染 React 树的根 DOM 容器中.\n\n引入官方提供的图片:\n\n![](https://zh-hans.reactjs.org/static/bb4b10114882a50090b8ff61b3c4d0fd/1e088/react_17_delegation.png)\n\n图中清晰的展示了`v17.0.0`的改动, 无论是在`document`还是`根 DOM 容器`上监听事件, 都可以归为`事件委托(代理)`([mdn](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Building_blocks/Events)).\n\n注意: `react`的事件体系, 不是全部都通过`事件委托`来实现的. 有一些[特殊情况](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMComponent.js#L530-L616), 是直接绑定到对应 DOM 元素上的(如:`scroll`, `load`), 它们都通过[listenToNonDelegatedEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L295-L314)函数进行绑定.\n\n上述特殊事件最大的不同是监听的 DOM 元素不同, 除此之外, 其他地方的实现与正常事件大体一致.\n\n本节讨论的是可以被`根 DOM 容器`代理的正常事件.\n\n## 事件绑定\n\n在前文[React 应用的启动过程](./bootstrap.md#create-global-obj)中介绍了`React`在启动时会创建全局对象, 其中在创建[fiberRoot](./bootstrap.md#create-root-impl)对象时, 调用[createRootImpl](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/client/ReactDOMRoot.js#L120-L169):\n\n```js\nfunction createRootImpl(\n  container: Container,\n  tag: RootTag,\n  options: void | RootOptions,\n) {\n  // ... 省略无关代码\n  if (enableEagerRootListeners) {\n    const rootContainerElement =\n      container.nodeType === COMMENT_NODE ? container.parentNode : container;\n    listenToAllSupportedEvents(rootContainerElement);\n  }\n  // ... 省略无关代码\n}\n```\n\n[listenToAllSupportedEvents](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L316-L349)函数, 实际上完成了事件代理:\n\n```js\n// ... 省略无关代码\nexport function listenToAllSupportedEvents(rootContainerElement: EventTarget) {\n  if (enableEagerRootListeners) {\n    // 1. 节流优化, 保证全局注册只被调用一次\n    if ((rootContainerElement: any)[listeningMarker]) {\n      return;\n    }\n    (rootContainerElement: any)[listeningMarker] = true;\n    // 2. 遍历allNativeEvents 监听冒泡和捕获阶段的事件\n    allNativeEvents.forEach((domEventName) => {\n      if (!nonDelegatedEvents.has(domEventName)) {\n        listenToNativeEvent(\n          domEventName,\n          false, // 冒泡阶段监听\n          ((rootContainerElement: any): Element),\n          null,\n        );\n      }\n      listenToNativeEvent(\n        domEventName,\n        true, // 捕获阶段监听\n        ((rootContainerElement: any): Element),\n        null,\n      );\n    });\n  }\n}\n```\n\n核心逻辑:\n\n1. 节流优化, 保证全局注册只被调用一次.\n2. 遍历`allNativeEvents`, 调用`listenToNativeEvent`监听冒泡和捕获阶段的事件.\n   - `allNativeEvents`包括了大量的原生事件名称, 它是在`DOMPluginEventSystem.js`中[被初始化](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L89-L93)\n\n[listenToNativeEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L351-L412):\n\n```js\n// ... 省略无关代码\nexport function listenToNativeEvent(\n  domEventName: DOMEventName,\n  isCapturePhaseListener: boolean,\n  rootContainerElement: EventTarget,\n  targetElement: Element | null,\n  eventSystemFlags?: EventSystemFlags = 0,\n): void {\n  let target = rootContainerElement;\n\n  const listenerSet = getEventListenerSet(target);\n  const listenerSetKey = getListenerSetKey(\n    domEventName,\n    isCapturePhaseListener,\n  );\n  // 利用set数据结构, 保证相同的事件类型只会被注册一次.\n  if (!listenerSet.has(listenerSetKey)) {\n    if (isCapturePhaseListener) {\n      eventSystemFlags |= IS_CAPTURE_PHASE;\n    }\n    // 注册事件监听\n    addTrappedEventListener(\n      target,\n      domEventName,\n      eventSystemFlags,\n      isCapturePhaseListener,\n    );\n    listenerSet.add(listenerSetKey);\n  }\n}\n```\n\n[addTrappedEventListener](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L468-L560):\n\n```js\n// ... 省略无关代码\nfunction addTrappedEventListener(\n  targetContainer: EventTarget,\n  domEventName: DOMEventName,\n  eventSystemFlags: EventSystemFlags,\n  isCapturePhaseListener: boolean,\n  isDeferredListenerForLegacyFBSupport?: boolean,\n) {\n  // 1. 构造listener\n  let listener = createEventListenerWrapperWithPriority(\n    targetContainer,\n    domEventName,\n    eventSystemFlags,\n  );\n  let unsubscribeListener;\n  // 2. 注册事件监听\n  if (isCapturePhaseListener) {\n    unsubscribeListener = addEventCaptureListener(\n      targetContainer,\n      domEventName,\n      listener,\n    );\n  } else {\n    unsubscribeListener = addEventBubbleListener(\n      targetContainer,\n      domEventName,\n      listener,\n    );\n  }\n}\n\n// 注册原生事件 冒泡\nexport function addEventBubbleListener(\n  target: EventTarget,\n  eventType: string,\n  listener: Function,\n): Function {\n  target.addEventListener(eventType, listener, false);\n  return listener;\n}\n\n// 注册原生事件 捕获\nexport function addEventCaptureListener(\n  target: EventTarget,\n  eventType: string,\n  listener: Function,\n): Function {\n  target.addEventListener(eventType, listener, true);\n  return listener;\n}\n```\n\n从`listenToAllSupportedEvents`开始, 调用链路比较长, 最后调用`addEventBubbleListener`和`addEventCaptureListener`监听了原生事件.\n\n### 原生 listener\n\n在注册原生事件的过程中, 需要重点关注一下监听函数, 即`listener`函数. 它实现了把原生事件派发到`react`体系之内, 非常关键.\n\n> 比如点击 DOM 触发原生事件, 原生事件最后会被派发到`react`内部的`onClick`函数. `listener`函数就是这个`由外至内`的关键环节.\n\n`listener`是通过`createEventListenerWrapperWithPriority`函数产生:\n\n```js\nexport function createEventListenerWrapperWithPriority(\n  targetContainer: EventTarget,\n  domEventName: DOMEventName,\n  eventSystemFlags: EventSystemFlags,\n): Function {\n  // 1. 根据优先级设置 listenerWrapper\n  const eventPriority = getEventPriorityForPluginSystem(domEventName);\n  let listenerWrapper;\n  switch (eventPriority) {\n    case DiscreteEvent:\n      listenerWrapper = dispatchDiscreteEvent;\n      break;\n    case UserBlockingEvent:\n      listenerWrapper = dispatchUserBlockingUpdate;\n      break;\n    case ContinuousEvent:\n    default:\n      listenerWrapper = dispatchEvent;\n      break;\n  }\n  // 2. 返回 listenerWrapper\n  return listenerWrapper.bind(\n    null,\n    domEventName,\n    eventSystemFlags,\n    targetContainer,\n  );\n}\n```\n\n可以看到, 不同的`domEventName`调用`getEventPriorityForPluginSystem`后返回不同的优先级, 最终会有 3 种情况:\n\n1. `DiscreteEvent`: 优先级最高, 包括`click, keyDown, input`等事件, [源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMEventProperties.js#L45-L80)\n   - 对应的`listener`是[dispatchDiscreteEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/ReactDOMEventListener.js#L121-L142)\n2. `UserBlockingEvent`: 优先级适中, 包括`drag, scroll`等事件, [源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMEventProperties.js#L100-L116)\n   - 对应的`listener`是[dispatchUserBlockingUpdate](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/ReactDOMEventListener.js#L144-L180)\n3. `ContinuousEvent`: 优先级最低,包括`animation, load`等事件, [源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMEventProperties.js#L119-L145)\n   - 对应的`listener`是[dispatchEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/ReactDOMEventListener.js#L182-L271)\n\n这 3 种`listener`实际上都是对[dispatchEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/ReactDOMEventListener.js#L182-L271)的包装:\n\n```js\n// ...省略无关代码\nexport function dispatchEvent(\n  domEventName: DOMEventName,\n  eventSystemFlags: EventSystemFlags,\n  targetContainer: EventTarget,\n  nativeEvent: AnyNativeEvent,\n): void {\n  if (!_enabled) {\n    return;\n  }\n  const blockedOn = attemptToDispatchEvent(\n    domEventName,\n    eventSystemFlags,\n    targetContainer,\n    nativeEvent,\n  );\n}\n```\n\n## 事件触发\n\n当原生事件触发之后, 首先会进入到`dispatchEvent`这个回调函数. 而`dispatchEvent`函数是`react`事件体系中最关键的函数, 其调用链路较长, 核心步骤如图所示:\n\n![](../../snapshots/synthetic-event/dispatch-event.png)\n\n重点关注其中 3 个核心环节:\n\n1. `attemptToDispatchEvent`\n2. `SimpleEventPlugin.extractEvents`\n3. `processDispatchQueue`\n\n### 关联 fiber\n\n[attemptToDispatchEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/ReactDOMEventListener.js#L274-L331)把原生事件和`fiber树`关联起来.\n\n```js\nexport function attemptToDispatchEvent(\n  domEventName: DOMEventName,\n  eventSystemFlags: EventSystemFlags,\n  targetContainer: EventTarget,\n  nativeEvent: AnyNativeEvent,\n): null | Container | SuspenseInstance {\n  // ...省略无关代码\n\n  // 1. 定位原生DOM节点\n  const nativeEventTarget = getEventTarget(nativeEvent);\n  // 2. 获取与DOM节点对应的fiber节点\n  let targetInst = getClosestInstanceFromNode(nativeEventTarget);\n  // 3. 通过插件系统, 派发事件\n  dispatchEventForPluginEventSystem(\n    domEventName,\n    eventSystemFlags,\n    nativeEvent,\n    targetInst,\n    targetContainer,\n  );\n  return null;\n}\n```\n\n核心逻辑:\n\n1. 定位原生 DOM 节点: 调用`getEventTarget`\n2. 获取与 DOM 节点对应的 fiber 节点: 调用`getClosestInstanceFromNode`\n3. 通过插件系统, 派发事件: 调用 `dispatchEventForPluginEventSystem`\n\n### 收集 fiber 上的 listener\n\n`dispatchEvent`函数的调用链路中, 通过不同的插件, 处理不同的事件. 其中最常见的事件都会由`SimpleEventPlugin.extractEvents`进行处理:\n\n```js\nfunction extractEvents(\n  dispatchQueue: DispatchQueue,\n  domEventName: DOMEventName,\n  targetInst: null | Fiber,\n  nativeEvent: AnyNativeEvent,\n  nativeEventTarget: null | EventTarget,\n  eventSystemFlags: EventSystemFlags,\n  targetContainer: EventTarget,\n): void {\n  const reactName = topLevelEventsToReactNames.get(domEventName);\n  if (reactName === undefined) {\n    return;\n  }\n  let SyntheticEventCtor = SyntheticEvent;\n  let reactEventType: string = domEventName;\n\n  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;\n  const accumulateTargetOnly = !inCapturePhase && domEventName === 'scroll';\n  // 1. 收集所有监听该事件的函数.\n  const listeners = accumulateSinglePhaseListeners(\n    targetInst,\n    reactName,\n    nativeEvent.type,\n    inCapturePhase,\n    accumulateTargetOnly,\n  );\n  if (listeners.length > 0) {\n    // 2. 构造合成事件, 添加到派发队列\n    const event = new SyntheticEventCtor(\n      reactName,\n      reactEventType,\n      null,\n      nativeEvent,\n      nativeEventTarget,\n    );\n    dispatchQueue.push({ event, listeners });\n  }\n}\n```\n\n核心逻辑:\n\n1. 收集所有`listener`回调\n\n   - 这里的是`fiber.memoizedProps.onClick/onClickCapture`等绑定在`fiber`节点上的回调函数\n   - 具体逻辑在[accumulateSinglePhaseListeners](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L712-L803):\n\n     ```js\n     export function accumulateSinglePhaseListeners(\n       targetFiber: Fiber | null,\n       reactName: string | null,\n       nativeEventType: string,\n       inCapturePhase: boolean,\n       accumulateTargetOnly: boolean,\n     ): Array<DispatchListener> {\n       const captureName = reactName !== null ? reactName + 'Capture' : null;\n       const reactEventName = inCapturePhase ? captureName : reactName;\n       const listeners: Array<DispatchListener> = [];\n\n       let instance = targetFiber;\n       let lastHostComponent = null;\n\n       // 从targetFiber开始, 向上遍历, 直到 root 为止\n       while (instance !== null) {\n         const { stateNode, tag } = instance;\n         // 当节点类型是HostComponent时(如: div, span, button等类型)\n         if (tag === HostComponent && stateNode !== null) {\n           lastHostComponent = stateNode;\n           if (reactEventName !== null) {\n             // 获取标准的监听函数 (如onClick , onClickCapture等)\n             const listener = getListener(instance, reactEventName);\n             if (listener != null) {\n               listeners.push(\n                 createDispatchListener(instance, listener, lastHostComponent),\n               );\n             }\n           }\n         }\n         // 如果只收集目标节点, 则不用向上遍历, 直接退出\n         if (accumulateTargetOnly) {\n           break;\n         }\n         instance = instance.return;\n       }\n       return listeners;\n     }\n     ```\n\n2. 构造合成事件(`SyntheticEvent`), 添加到派发队列(`dispatchQueue`)\n\n### 构造合成事件\n\n[SyntheticEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/SyntheticEvent.js#L152), 是`react`内部创建的一个对象, 是原生事件的跨浏览器包装器, 拥有和浏览器原生事件相同的接口(`stopPropagation`,`preventDefault`), 抹平不同浏览器 api 的差异, 兼容性好.\n\n具体的构造过程并不复杂, 可以直接[查看源码](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/SyntheticEvent.js#L28-L136).\n\n此处我们需要知道, 在`Plugin.extractEvents`过程中, 遍历`fiber树`找到`listener`之后, 就会创建`SyntheticEvent`, 加入到`dispatchQueue`中, 等待派发.\n\n### 执行派发\n\n`extractEvents`完成之后, 逻辑来到[processDispatchQueue](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L260-L272), 终于要真正执行派发了.\n\n```js\nexport function processDispatchQueue(\n  dispatchQueue: DispatchQueue,\n  eventSystemFlags: EventSystemFlags,\n): void {\n  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;\n  for (let i = 0; i < dispatchQueue.length; i++) {\n    const { event, listeners } = dispatchQueue[i];\n    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);\n  }\n  // ...省略无关代码\n}\n\nfunction processDispatchQueueItemsInOrder(\n  event: ReactSyntheticEvent,\n  dispatchListeners: Array<DispatchListener>,\n  inCapturePhase: boolean,\n): void {\n  let previousInstance;\n  if (inCapturePhase) {\n    // 1. capture事件: 倒序遍历listeners\n    for (let i = dispatchListeners.length - 1; i >= 0; i--) {\n      const { instance, currentTarget, listener } = dispatchListeners[i];\n      if (instance !== previousInstance && event.isPropagationStopped()) {\n        return;\n      }\n      executeDispatch(event, listener, currentTarget);\n      previousInstance = instance;\n    }\n  } else {\n    // 2. bubble事件: 顺序遍历listeners\n    for (let i = 0; i < dispatchListeners.length; i++) {\n      const { instance, currentTarget, listener } = dispatchListeners[i];\n      if (instance !== previousInstance && event.isPropagationStopped()) {\n        return;\n      }\n      executeDispatch(event, listener, currentTarget);\n      previousInstance = instance;\n    }\n  }\n}\n```\n\n在[processDispatchQueueItemsInOrder](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L233-L258)遍历`dispatchListeners`数组, 执行[executeDispatch](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/DOMPluginEventSystem.js#L222-L231)派发事件, 在`fiber`节点上绑定的`listener`函数被执行.\n\n在`processDispatchQueueItemsInOrder`函数中, 根据`捕获(capture)`或`冒泡(bubble)`的不同, 采取了不同的遍历方式:\n\n1. `capture`事件: `从上至下`调用`fiber树`中绑定的回调函数, 所以`倒序`遍历`dispatchListeners`.\n2. `bubble`事件: `从下至上`调用`fiber树`中绑定的回调函数, 所以`顺序`遍历`dispatchListeners`.\n\n## 总结\n\n从架构上来讲, [SyntheticEvent](https://github.com/facebook/react/blob/v17.0.2/packages/react-dom/src/events/SyntheticEvent.js#L152)打通了从外部`原生事件`到内部`fiber树`的交互渠道, 使得`react`能够感知到浏览器提供的`原生事件`, 进而做出不同的响应, 修改`fiber树`, 变更视图等.\n\n从实现上讲, 主要分为 3 步:\n\n1. 监听原生事件: 对齐`DOM元素`和`fiber元素`\n2. 收集`listeners`: 遍历`fiber树`, 收集所有监听本事件的`listener`函数.\n3. 派发合成事件: 构造合成事件, 遍历`listeners`进行派发.\n"
  },
  {
    "path": "docs/main/workloop.md",
    "content": "---\ntitle: 两大工作循环\ngroup: 基本概念\norder: 1\n---\n\n# React 工作循环 (workLoop)\n\n在前文([React 应用的宏观包结构](./macro-structure.md))中, 介绍了`react`核心包之间的依赖和调用关系, 并绘制出了概览图. 在概览图中, 可以看到有两个大的循环, 它们分别位于`scheduler`和`react-reconciler`包中:\n\n![](../../snapshots/workloop.png)\n\n本文将这两个循环分别表述为`任务调度循环`和`fiber构造循环`. 接下来从宏观角度阐述这两大循环的作用, 以及它们之间的区别和联系. 更深入的源码分析分别在`scheduler 调度机制`和`fiber 树构造`章节中详细解读.\n\n1. `任务调度循环`\n\n源码位于[`Scheduler.js`](https://github.com/facebook/react/blob/v17.0.2/packages/scheduler/src/Scheduler.js), 它是`react`应用得以运行的保证, 它需要循环调用, 控制所有任务(`task`)的调度.\n\n2. `fiber构造循环`\n\n源码位于[`ReactFiberWorkLoop.js`](https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js), 控制 fiber 树的构造, 整个过程是一个[深度优先遍历](../algorithm/dfs.md).\n\n这两个循环对应的 js 源码不同于其他闭包(运行时就是闭包), 其中定义的全局变量, 不仅是该作用域的私有变量, 更用于`控制react应用的执行过程`.\n\n## 区别与联系\n\n1. 区别\n\n   - `任务调度循环`是以`二叉堆`为数据结构(详见[react 算法之堆排序](../algorithm/heapsort.md)), 循环执行`堆`的顶点, 直到`堆`被清空.\n   - `任务调度循环`的逻辑偏向宏观, 它调度的是每一个任务(`task`), 而不关心这个任务具体是干什么的(甚至可以将`Scheduler`包脱离`react`使用), 具体任务其实就是执行回调函数`performSyncWorkOnRoot`或`performConcurrentWorkOnRoot`.\n   - `fiber构造循环`是以`树`为数据结构, 从上至下执行深度优先遍历(详见[react 算法之深度优先遍历](../algorithm/dfs.md)).\n   - `fiber构造循环`的逻辑偏向具体实现, 它只是任务(`task`)的一部分(如`performSyncWorkOnRoot`包括: `fiber`树的构造, `DOM`渲染, 调度检测), 只负责`fiber`树的构造.\n\n2. 联系\n   - `fiber构造循环`是`任务调度循环`中的任务(`task`)的一部分. 它们是从属关系, 每个任务都会重新构造一个`fiber`树.\n\n## 主干逻辑\n\n通过上文的描述, 两大循环的分工可以总结为: 大循环(任务调度循环)负责调度`task`, 小循环(fiber 构造循环)负责实现`task` .\n\nreact 运行的主干逻辑, 即将`输入转换为输出`的核心步骤, 实际上就是围绕这两大工作循环进行展开.\n\n结合上文的宏观概览图(展示核心包之间的调用关系), 可以将 react 运行的主干逻辑进行概括:\n\n1. 输入: 将每一次更新(如: 新增, 删除, 修改节点之后)视为一次`更新需求`(目的是要更新`DOM`节点).\n2. 注册调度任务: `react-reconciler`收到`更新需求`之后, 并不会立即构造`fiber树`, 而是去调度中心`scheduler`注册一个新任务`task`, 即把`更新需求`转换成一个`task`.\n3. 执行调度任务(输出): 调度中心`scheduler`通过`任务调度循环`来执行`task`(`task`的执行过程又回到了`react-reconciler`包中).\n   - `fiber构造循环`是`task`的实现环节之一, 循环完成之后会构造出最新的 fiber 树.\n   - `commitRoot`是`task`的实现环节之二, 把最新的 fiber 树最终渲染到页面上, `task`完成.\n\n主干逻辑就是`输入到输出`这一条链路, 为了更好的性能(如`批量更新`, `可中断渲染`等功能), `react`在输入到输出的链路上做了很多优化策略, 比如本文讲述的`任务调度循环`和`fiber构造循环`相互配合就可以实现`可中断渲染`.\n\n## 总结\n\n本节从宏观角度描述了`react`源码中的两大工作循环. 通过这两个大循环概括出`react`运行的主干逻辑. `react-reconciler`和`Scheduler`包代码量多且逻辑复杂, 但实际上大部分都是服务于这个主干. 了解这两大循环, 更容易理解`react`的整体运行链路.\n"
  },
  {
    "path": "licence",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>."
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-illustration-series\",\n  \"version\": \"0.0.1\",\n  \"description\": \"A static site based on dumi\",\n  \"license\": \"GPL-3.0\",\n  \"scripts\": {\n    \"build\": \"dumi build\",\n    \"dev\": \"dumi dev\",\n    \"prepare\": \"husky install && dumi setup\",\n    \"start\": \"npm run dev\"\n  },\n  \"commitlint\": {\n    \"extends\": [\n      \"@commitlint/config-conventional\"\n    ]\n  },\n  \"lint-staged\": {\n    \"*.{md,json}\": [\n      \"prettier --write --no-error-on-unmatched-pattern\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^17.1.2\",\n    \"@commitlint/config-conventional\": \"^17.1.0\",\n    \"dumi\": \"^2.2.0\",\n    \"husky\": \"^8.0.1\",\n    \"lint-staged\": \"^13.0.3\",\n    \"prettier\": \"^2.7.1\"\n  },\n  \"authors\": [\n    \"tlyspa@gmail.com\"\n  ]\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# 图解 React 源码系列\n\n> `react`源码, 基于[`react@17.0.2`](https://github.com/facebook/react/tree/v17.0.2)(尽可能跟随 react 版本的升级, 持续更新). 用大量配图的方式, 致力于将`react`原理表述清楚.\n\n## 使用指南\n\n1. 本系列以 react 核心包结构和运行机制为主线索进行展开. 包括`react 宏观结构`, `react 工作循环`, `react 启动模式`, `react fiber原理`, `react hook原理`, `react 合成事件`等核心内容.\n2. 开源作品需要社区的净化和参与, 如有表述不清晰或表述错误, 欢迎[issue 勘误](https://github.com/7kms/react-illustration-series/issues). 如果对你有帮助, 请不吝 star.\n3. 本系列最初写作于 2020 年 6 月(当时稳定版本是 v16.13.1), 随着 react 官方的升级, 本 repo 会将主要版本的文章保存在以版本号命名的分支中.\n4. 当下前端技术圈总体比较浮躁, 各技术平台充斥着不少\"标题党\". 真正对于技术本身, 不能急于求成, 需要静下心来修炼.\n5. 本系列不是面经, 但会列举一些面试题来加深对 react 理解.\n6. 本系列所有内容皆为原创, 如需转载, 请注明出处.\n\n## 适用读者\n\n1. 对`react`,`react-dom`开发 web 应用有实践经验.\n2. 期望深入理解`react`内在作用原理.\n\n---\n\n## 版本跟踪\n\n> 本系列暂时只跟踪稳定版本的变动. `react`仓库代码改动比较频繁, 在写作过程中, 如果伴随小版本的发布, 文章中的源码链接会以写作当天的`最新小版本`为基准.\n\n- [`react@17.0.0`](https://github.com/facebook/react/releases/tag/v17.0.0)作为主版本升级, 相较于 16.x 版本, 在使用层面基本维持不变, 在源码层面需要关注的重大的变动如下\n\n\n    | 重大变动                                                      | 所属板块                                    | 官方解释                                                                                                      |\n    | ------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |\n    | 重构`Fiber.expirationTime`并引入`Fiber.lanes`                 | `react-reconciler`                          | [Initial Lanes implementation #18796](https://github.com/facebook/react/pull/18796)                           |\n    | 事件代理节点从 document 变成 rootNode, 取消合成事件的缓存池等 | `legacy-events(被移除)`, `react-dom/events` | [changes-to-event-delegation](https://reactjs.org/blog/2020/10/20/react-v17.html#changes-to-event-delegation) |\n\n- [`react@17.0.1`](https://github.com/facebook/react/releases/tag/v17.0.1)相较于主版本`v17.0.0`做了一个点的优化, [改动了 1 个文件](https://github.com/facebook/react/compare/v17.0.0...v17.0.1), 修复 ie11 兼容问题, 同时提升 v8 内部的执行性能.\n\n* [`react@17.0.2`](https://github.com/facebook/react/releases/tag/v17.0.2)相较于`v17.0.1`, 改动集中于`Scheduler`包, 主干逻辑没有变动, 只与调度[性能统计相关](https://github.com/facebook/react/compare/v17.0.1...v17.0.2).\n\n## 主要内容\n\n### 基本概念\n\n- [宏观包结构](./docs/main/macro-structure.md)\n- [两大工作循环](./docs/main/workloop.md)\n- [高频对象](./docs/main/object-structure.md)\n\n### 运行核心\n\n- [reconciler 运作流程](./docs/main/reconciler-workflow.md)\n- [启动过程](./docs/main/bootstrap.md)\n- [优先级管理](./docs/main/priority.md)\n- [scheduler 调度原理](./docs/main/scheduler.md)\n- [fiber 树构造(基础准备)](./docs/main/fibertree-prepare.md)\n- [fiber 树构造(初次创建)](./docs/main/fibertree-create.md)\n- [fiber 树构造(对比更新)](./docs/main/fibertree-update.md)\n- [fiber 树渲染](./docs/main/fibertree-commit.md)\n- 异常处理\n\n### 数据管理\n\n- [状态与副作用](./docs/main/state-effects.md)\n- [hook 原理(概览)](./docs/main/hook-summary.md)\n- [hook 原理(状态 Hook)](./docs/main/hook-state.md)\n- [hook 原理(副作用 Hook)](./docs/main/hook-effect.md)\n- [context 原理](./docs/main/context.md)\n\n### 交互\n\n- [合成事件原理](./docs/main/synthetic-event.md)\n\n### 高频算法\n\n- [位运算](./docs/algorithm/bitfield.md)\n- [堆排序](./docs/algorithm/heapsort.md)\n- [深度优先遍历](./docs/algorithm/dfs.md)\n- [链表操作](./docs/algorithm/linkedlist.md)\n- [栈操作](./docs/algorithm/stack.md)\n- [diff 算法](./docs/algorithm/diff.md)\n\n## 历史版本\n\n- [基于 v16.13.1 版本的分析](https://github.com/7kms/react-illustration-series/tree/v16.13.1)\n- [基于 v17.0.1 版本的分析](https://github.com/7kms/react-illustration-series/tree/v17.0.1)\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@@/*\": [\".dumi/tmp/*\"]\n    }\n  },\n  \"include\": [\".dumi/**/*\", \".dumirc.ts\"]\n}\n"
  }
]