惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
The Last Watchdog
The Last Watchdog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
O
OpenAI News
T
Threat Research - Cisco Blogs
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Palo Alto Networks Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Help Net Security
P
Proofpoint News Feed
MyScale Blog
MyScale Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
Securelist
Vercel News
Vercel News
S
Security Affairs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Blog — PlanetScale
Blog — PlanetScale
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Last Week in AI
Last Week in AI
博客园_首页
Attack and Defense Labs
Attack and Defense Labs
G
Google Developers Blog
T
Tor Project blog
Project Zero
Project Zero
腾讯CDC
Schneier on Security
Schneier on Security
月光博客
月光博客
N
Netflix TechBlog - Medium
AWS News Blog
AWS News Blog
L
LINUX DO - 最新话题
P
Proofpoint News Feed
博客园 - 司徒正美
A
About on SuperTechFans
Latest news
Latest news
Scott Helme
Scott Helme
Hacker News: Ask HN
Hacker News: Ask HN
T
Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
C
CERT Recently Published Vulnerability Notes
Google DeepMind News
Google DeepMind News
博客园 - 聂微东

szhshp 的第三边境研究所

假设, AI 把我代替的那一刻真的到来 | szhshp 的第三边境研究所 小傻瓜都能懂的 AstrBot QQ 机器人集成 MCP 功能实战指南 | szhshp 的第三边境研究所 《智人之上: 从石器时代到 AI 时代的信息网络简史》阅读笔记 | szhshp 的第三边境研究所 iPadOS 26 无法设置空间场景图片壁纸的解决方法 | szhshp 的第三边境研究所 《一路云海》(终) | szhshp 的第三边境研究所 《一路云海》(四): 如何不按套路旅行 | szhshp 的第三边境研究所 《一路云海》(三): In Ya Mellow Tone | szhshp 的第三边境研究所 《一路云海》(二): 关西世博参观纪实 | szhshp 的第三边境研究所 《一路云海》(一): 新的征程 | szhshp 的第三边境研究所 2025 大阪世博会 [ 3 天前-先到先得 ] 阶段 场馆预约必中独家攻略 | szhshp 的第三边境研究所 Hackathon 随想 | szhshp 的第三边境研究所 一杯双皮奶 | szhshp 的第三边境研究所 Armbian + CasaOS + NAS 配置指南 | szhshp 的第三边境研究所 Docker 构建镜像报错: error getting credentials - err: exit status 1, out: `` | szhshp 的第三边境研究所 Disqus RIP! 论过高的维护成本如何治疗固执的坏习惯 | szhshp 的第三边境研究所 炸弹猫桌游变体规则 | szhshp 的第三边境研究所 《小岛经济学》阅读笔记 | szhshp 的第三边境研究所 《金钱心理学》阅读笔记 | szhshp 的第三边境研究所 为知笔记 RIP: 迁移剩余的笔记 | szhshp 的第三边境研究所 2025 博客第十年展望 - 再见我的过去 | szhshp 的第三边境研究所 我在独立游戏里面致敬的作品 | szhshp 的第三边境研究所 《How to make thing faster》阅读笔记 | szhshp 的第三边境研究所 《The Art of Clean Code》阅读笔记 | szhshp 的第三边境研究所 《Clean Architecture: A Craftsman Guide to Software Structure and Design》阅读笔记 | szhshp 的第三边境研究所 《How AI Works》阅读笔记 | szhshp 的第三边境研究所 游戏策划废案 - Project Uranus | szhshp 的第三边境研究所 游戏策划废案 - Project X | szhshp 的第三边境研究所 人生第一款独立游戏开发复盘 | szhshp 的第三边境研究所 Trap of Life | szhshp 的第三边境研究所 如果我用手搓了个暗物质雏形 | szhshp 的第三边境研究所 《Rust Programming Language》- EX - Cheatsheet | szhshp 的第三边境研究所 新的播放器上线了 | szhshp 的第三边境研究所 Cypress - End-to-End Testing: OKTA Authentication with MFA | szhshp 的第三边境研究所 VSC x VIM - 反正多学几个快捷键没有坏处 | szhshp 的第三边境研究所 两小时 Docker 速成 - Getting Started | szhshp 的第三边境研究所 《The Joy of Javascript》- 5 - Data | szhshp 的第三边境研究所 《The Joy of Javascript》- 4 - Meta Programming | szhshp 的第三边境研究所 《The Joy of Javascript》- 3 - ADT(Algebraic Data Type) | szhshp 的第三边境研究所 Web-Automation Testing | szhshp 的第三边境研究所 《The Joy of Javascript》- 2 - Functor/Monad | szhshp 的第三边境研究所 《The Joy of Javascript》- 1 - Object/Function | szhshp 的第三边境研究所 EvalError: Possible side-effect in debug-evaluate | szhshp 的第三边境研究所 前端 QA 工具链指南 - husky,commitlint,commitizen,eslint,typescript,express | szhshp 的第三边境研究所 效率工具 & 软件推荐 | szhshp 的第三边境研究所 站点开源 Project Titan | szhshp 的第三边境研究所 cmd/powershell-命令速查-Cheatsheet | szhshp 的第三边境研究所 Git-命令速查与相关问题解决-Cheatsheet & Troubleshooting | szhshp 的第三边境研究所 Tailwind.css not included in NextJS Production | szhshp 的第三边境研究所 Github GraphQL API - Data Integration | szhshp 的第三边境研究所 尝试 Windows Terminal + z.lua | szhshp 的第三边境研究所 StrokePlus.net - Powerful Hotkey Boost | szhshp 的第三边境研究所 优雅地 Hack 一台黑苹果 | szhshp 的第三边境研究所 一杯关东煮 | szhshp 的第三边境研究所 Gitee x PicGo 管理图床 | szhshp 的第三边境研究所 Create Your Own Redux (Typescript) | szhshp 的第三边境研究所 Getting Started: Ionic + Capacitor + React + Typescript + Android | szhshp 的第三边境研究所 GraphQL: File Upload & Troubleshooting | szhshp 的第三边境研究所 吉祥航空随心飞刷票工具循环查票工具 | szhshp 的第三边境研究所 NVM Windows Portable Installation | szhshp 的第三边境研究所 NextJS x Typescript - Integration & Troubleshooting | szhshp 的第三边境研究所 全站点升级 - Project Titan | szhshp 的第三边境研究所 AWS-EC2-常见问题解决-Troubleshooting | szhshp 的第三边境研究所 Workbox-Getting Started | szhshp 的第三边境研究所 《时间简史》阅读笔记 | szhshp 的第三边境研究所 《时间的秩序》阅读笔记 | szhshp 的第三边境研究所 ESLint-常见问题解决-Troubleshooting | szhshp 的第三边境研究所 站点索引 | szhshp 的第三边境研究所 个人知识库搭建 - Hexo 实现 Github/Coding 持续集成 | szhshp 的第三边境研究所 BMOB-常见问题解决-Troubleshooting | szhshp 的第三边境研究所 React + Typescript: 开启 HMR/Hot Loader | szhshp 的第三边境研究所 Yarn & NPM | szhshp 的第三边境研究所 ESLint: Typescript + React + TSX 集成 | szhshp 的第三边境研究所 《10101》EP15:过年 2020 | szhshp 的第三边境研究所 Cmder-Usage | szhshp 的第三边境研究所 Redux-DOM not refreshed | szhshp 的第三边境研究所 React Semantic UI-CheatSheet | szhshp 的第三边境研究所 Typescript: Getting Started | szhshp 的第三边境研究所 Flutter-Getting Started | szhshp 的第三边境研究所 Typescript: Access window object with type check | szhshp 的第三边境研究所 VSC Extension Development-Create A Code Formatter Extension | szhshp 的第三边境研究所 OneNote Extension-Main API | szhshp 的第三边境研究所 TortoiseGit Win10 Icon not showing correctly | szhshp 的第三边境研究所 Mongoose: aggregate() 方法实现聚合函数 | szhshp 的第三边境研究所 MongoDB x Mongoose: 实现类似 Join 的功能 | szhshp 的第三边境研究所 Disqus 评论框改造工程-实现 Disqus 代理的 Serverless 服务 | szhshp 的第三边境研究所 GUI, CLI, Terminal, Shell, TTY, Console, Bash | szhshp 的第三边境研究所 Jest Async Best Practise: No Nested Async | szhshp 的第三边境研究所 关系型数据库与非关系型数据库优缺点分析 | szhshp 的第三边境研究所 New Edge & Chromium Error - Package is invalid: 'CRX_VERSION_NUMBER_INVALID' | szhshp 的第三边境研究所 选择 GraphQL 的 N 个理由 | szhshp 的第三边境研究所 StrokePlus - 鼠标手势神器的代码编写技巧 | szhshp 的第三边境研究所 嗨,好久不见 | szhshp 的第三边境研究所 Vue + ElementUI 集成 Vue Router | szhshp 的第三边境研究所 IE8 - Unknown Runtime Error Using innerHTML & Ajax | szhshp 的第三边境研究所 React: Handling Events | szhshp 的第三边境研究所 React-Navigation Integration with Redux | szhshp 的第三边境研究所 React: States is tricky | szhshp 的第三边境研究所 《10101》EP14:小剧场 | szhshp 的第三边境研究所 ColdFusion - Basic - arguments in cffunction | szhshp 的第三边境研究所 ColdFusion - Basic - cfset | szhshp 的第三边境研究所
React In Depth - React 源码解析 - render() & Fiber | szhshp 的第三边境研究所
2019-08-10 · via szhshp 的第三边境研究所

Meta

目录

Preparation

V16.8.6

克隆react源码, github 地址:https://github.com/facebook/react.git

安装 gulp, npm 有些版本会报错所以我们直接用 gulp

npm install gulp -g
gulp install
gulp build

然后 build 文件夹就可以看到 react 的源码

First Glance

JSX 其实就是 React.createElement(component, props, ...children) 的语法糖, 最终就会被 Babel 解析成 React.createElement() 方法

V16 之后, 每一个 Component 都会使用 createElement() 方法处理成 ReactElement 实例

一般我们的渲染会从这一段开始:

ReactDOM.render( < App / > , document.getElementById('root'));
// 我们使用 `ReactDOM.render()` 的时候就会将 App 这个 ReactElement 传给第一个参数

ReactDOM.render()

我们看一下 ReactDOM.render() 的代码:

render: function(element, container, callback) {
  ...
  return
  legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
},

可以看到最终调用了 legacyRenderSubtreeIntoContainer 方法:

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  /* 省略 warning 部分 */
  var root = container._reactRootContainer;
  var fiberRoot = void 0;
  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      /* 省略 callback 处理部分 */
    }
    // Initial mount should not be batched.
    unbatchedUpdates(function() {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      /* 省略 callback 处理部分 */
    }
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

所以实际上做了以下操作:

  1. 这个渲染首先查看一下是否是根节点
  2. 如果是根节点的话, 那就是初次加载, 执行 initial mount, 然后执行非批量更新 (unbatchUpdates + updateContainer)
  3. 如果不是根节点那么久已经加载了, 批量执行更新操作 (updateContainer)

legacyCreateRootFromDOMContainer()

然后我们看向 initial mount 时执行的函数 legacyCreateRootFromDOMContainer()

function legacyCreateRootFromDOMContainer(container, forceHydrate) {

  /* 省略 hydrate 逻辑 */

  return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
}

最终 root 会获得一个 ReactSyncRoot

function ReactSyncRoot(container, tag, hydrate) {
  // 根据 container 生成一个 FiberRoot
  var root = createContainer(container, tag, hydrate);
  this._internalRoot = root;
}

function createContainer(containerInfo, tag, hydrate) {
  return createFiberRoot(containerInfo, tag, hydrate);
}

function createFiberRoot(containerInfo, tag, hydrate) {
  var root = new FiberRootNode(containerInfo, tag, hydrate);

  var uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  return root;
}

然后最终可以看到 _internalRoot 最终等于一个 FiberRootNode .

Fiber

A Fiber is work on a Component that needs to be done or was done. There can be more than one per component.

一个 Fiber 对应一个或多个 Component, Fiber 保存了和 DOM 树一样的结构

组件的各种操作就由 Fiber 节点负责, Fiber 会通过碎片化模式管理更新过程, 其中包含了各种事务优先级管理以及调配, 使得大量的计算可以被拆解以及异步化

这种分片设计极大地提升了效率。

Filber 控制的更新分为以下两个阶段:

  1. render/reconciliation: 复用 current 上的 Fiber 数据结构来一步地(通过requestIdleCallback)来构建新的 tree,标记处需要更新的节点,放入队列中。
  2. commit: React 将其所有的变更一次性更新到DOM上。

构建完成后的 Fiber Tree 的结构

定用这样的 root 节点在控制台输入代码:

document.getElementById('root')._reactRootContainer._internalRoot

<div id="root">
  <div class="App">
    <header class="App-header">
      <img src="" class="App-logo" alt="logo">
      <p>Hellow</p><a>React</a>
    </header>
  </div>
</div>

updateContainer()

获得 root 之后我们会执行 updateContainer() , 调用逻辑:

  1. updateContainer(element, container, parentComponent, callback)

    • 这里面会使用 computeExpirationForFiber() 计算出 expirationTime
    • 同步任务的 expirationTime 会是最大值
    • expirationTime 的值越大那么优先级越高
  2. updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback)

    • 刚才运算得出来的时间传到这个函数里边
    • 调用 scheduleRootUpdate()
  3. scheduleRootUpdate(current$1, element, expirationTime, suspenseConfig, callback)

scheduleRootUpdate()

function scheduleRootUpdate(current$1, element, expirationTime, suspenseConfig, callback) {
  /* 省略 warning */
  var update = createUpdate(expirationTime, suspenseConfig);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {
    element: element
  };

  /* 省略 callback handling*/
  enqueueUpdate(current$1, update);
  scheduleWork(current$1, expirationTime);

  return expirationTime;
}

这个函数又做了这么几个操作:

  1. 创建一个 update

  2. enqueueUpdate(fiber, update)

    • 这里面通过各种判断进行懒处理创建队列
    • 里面会对 fiber 的 alternative 进行检查,如果已经有了的话可以拷贝过来
    • 最终会给这个 fiber 创建一个自己的队列 fiber.updateQueue
  3. scheduleWork()

接下来我们主要关注这个 scheduleWork 函数, 我们可以看到这个函数就是 scheduleUpdateOnFiber 那么 scheduleUpdateOnFiber 做了什么事情呢?

function scheduleUpdateOnFiber(fiber, expirationTime) {
  checkForNestedUpdates();
  warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
  // 获取到 FiberRoot
  var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
  if (root === null) {
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }
  root.pingTime = NoWork;
  checkForInterruption(fiber, expirationTime);
  recordScheduleUpdate();
  var priorityLevel = getCurrentPriorityLevel();
  // 如果是同步事件
  if (expirationTime === Sync) {
    if (
      // 如果是非批量事件
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // Check if we're not already rendering
      (executionContext & (RenderContext | CommitContext)) === NoContext) {
      schedulePendingInteractions(root, expirationTime);
      // 那么就执行 renderRoot
      var callback = renderRoot(root, Sync, true);
      while (callback !== null) {
        callback = callback(true);
      }
    } else {
      // 否则就执行 scheduleCallbackForRoot
      scheduleCallbackForRoot(root, ImmediatePriority, Sync);
      if (executionContext === NoContext) {
        flushSyncCallbackQueue();
      }
    }
  } else {
    // 如果不是同步事件那么也执行 scheduleCallbackForRoot
    scheduleCallbackForRoot(root, priorityLevel, expirationTime);
  }
  /* ... */
}

这上面有几个比较核心的逻辑: 1. 同步任务并且非批量任务: 直接执行 renderRoot() 2. 其他情况都会去执行 scheduleCallbackForRoot() 然后其实 scheduleCallbackForRoot() 最终也会将 renderRoot 作为 callback 所以归为一条线.

RenderRoot

renderRoot 方法非常长, 我们关注这部分:

do {
  try {
    if (isSync) {
      workLoopSync();
    } else {
      workLoop();
    }
    break;
  } catch (thrownValue) {
    /* ... */
  }
} while (true);

主要其实是 WorkLoop 这部分进行了循环工作调度

下一篇详细观察一下 WorkLoop 以及如何 render 到实际 DOM

到此为止的流程图

参考文献