React 源码揭秘:单节点 Diff 与 `reconcileSingleElement` 的复用判定逻辑
文档前端【免费下载链接】just-react「React技术揭秘」 一本自顶向下的React源码分析书项目地址https://gitcode.com/gh_mirrors/ju/just-react点击查看免费下载本篇技术指南聚焦「React 技术揭秘」just-react仓库中 docs/diff/one.md 讲解的核心主题——单一节点单元素的 Diff 算法即reconcileSingleElement如何判断新旧JSX对应的DOM 节点是否可以复用。文中将结合本仓库 docs/diff/prepare.md、docs/process/beginWork.md 等章节从调用入口、判定流程、删除语义到练习题完整梳理单节点 Diff 的源码实现与设计动机。读完本文你将能够徒手写出reconcileSingleElement的核心判定逻辑并准确解释key与type在节点复用中的各自作用。从reconcileChildFibers到reconcileSingleElement在 docs/diff/prepare.md 中我们了解到Diff 算法是Reconciler的核心对于update的组件beginWork会将其与该组件上次更新时对应的Fiber 节点即current fiber进行比较比较的结果生成新的Fiber 节点即workInProgress fiber。Diff 的入口函数是reconcileChildFibers它会根据newChild即本次更新的JSX 对象的类型分发到不同的处理函数function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, ): Fiber | null { const isObject typeof newChild object newChild ! null; if (isObject) { // object 类型可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPE switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: // 调用 reconcileSingleElement 处理 // ...其他 case } } if (typeof newChild string || typeof newChild number) { // 调用 reconcileSingleTextNode 处理 // ...省略 } if (isArray(newChild)) { // 调用 reconcileChildrenArray 处理多节点 Diff见 docs/diff/multi.md // ...省略 } // 以上都没有命中删除节点 return deleteRemainingChildren(returnFiber, currentFirstChild); }按照同级节点的数量Diff 被划分为两类单节点 DiffnewChild类型为object、number或string代表同级只有一个节点进入reconcileSingleElement/reconcileSingleTextNode多节点 DiffnewChild类型为Array同级有多个节点进入reconcileChildrenArray详见 docs/diff/multi.md。本文讨论的正是第一种情况以类型objectREACT_ELEMENT_TYPE为例它最终会进入reconcileSingleElement。reconcileSingleElement做了什么reconcileSingleElement是单节点 Diff 的核心函数它接收三个参数returnFiber当前正在执行beginWork的父级Fiber 节点currentFirstChild上一次更新中第一个子Fiber 节点单链表头element本次更新的ReactElement即JSX 对象。其完整执行流程可以概括为两步遍历上一次更新的兄弟 fiber 链寻找本次element能否复用的DOM 节点根据能否复用决定返回能复用则返回复用的fiber不能复用则删除旧节点并新建Fiber源码中创建新Fiber的部分省略。function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement ): Fiber { const key element.key; let child currentFirstChild; // 首先判断是否存在对应 DOM 节点 while (child ! null) { // 上一次更新存在 DOM 节点接下来判断是否可复用 // 首先比较 key 是否相同 if (child.key key) { // key 相同接下来比较 type 是否相同 switch (child.tag) { // ...省略 case default: { if (child.elementType element.type) { // type 相同则表示可以复用 // 返回复用的 fiber return existing; } // type 不同则跳出 switch break; } } // 代码执行到这里代表key 相同但是 type 不同 // 将该 fiber 及其兄弟 fiber 标记为删除 deleteRemainingChildren(returnFiber, child); break; } else { // key 不同将该 fiber 标记为删除 deleteChild(returnFiber, child); } child child.sibling; } // 创建新 Fiber并返回 ...省略 }可以看到这个函数做的事情就是拿着本次更新的element遍历上一次更新的fiber单链表判断能否复用并在决定不复用时清理旧节点。React 预设的三个限制复用判定的前提在 docs/diff/prepare.md 中强调过React 为了把 Diff 的算法复杂度从 O(n³) 降下来预设了三条限制只对同级元素进行 Diff——节点跨越层级就不会尝试复用两个不同类型的元素会产生出不同的树——元素由div变为pReact 会销毁div及其子孙节点并新建p的树开发者可以通过key prop暗示哪些子元素在不同的渲染下能保持稳定。单节点 Diff 的代码正是这三条限制的直接体现从代码可以看出React 通过先判断key是否相同如果key相同则判断type是否相同只有key与type都相同时一个DOM 节点才能复用。删除语义的两个关键细节代码中有两处删除调用它们的语义差异值得细读当child ! null且key 相同且type 不同时执行deleteRemainingChildren(returnFiber, child)将child及其兄弟 fiber 全部标记删除当child ! null且key 不同时仅执行deleteChild(returnFiber, child)将child这一个 fiber 标记删除然后child child.sibling继续遍历。为什么会有这种差别考虑如下例子// 当前页面显示的 ul li * 3 // 这次需要更新的 ul p本次更新只有一个p属于单一节点的 Diff。在reconcileSingleElement中会遍历之前的 3 个fiber对应 DOM 为 3 个li寻找本次的p是否可以复用其中某个fiber的DOM。当key相同且type不同时代表已经找到了本次p对应的上次的fiber但p与li的type不同不能复用。既然唯一可能复用的候选已经排除了剩下的fiber就都没有机会了所以需要把child及其所有兄弟都标记删除deleteRemainingChildren。当key不同时只代表当前遍历到的这个fiber不能被p复用后面还有兄弟fiber尚未遍历到所以只标记当前这一个fiber删除deleteChild并继续向后遍历。一句话总结type不同意味着没希望了全删key不同只是这一个不行再看看下一个。从beginWork看 Diff 的触发时机单节点 Diff 并非凭空发生它由 render 阶段递过程中的beginWork驱动。在 docs/process/beginWork.md 中可以看到对于常见的FunctionComponent/ClassComponent/HostComponentbeginWork最终会进入reconcileChildrenexport function reconcileChildren( current: Fiber | null, workInProgress: Fiber, nextChildren: any, renderLanes: Lanes ) { if (current null) { // 对于 mount 的组件创建新的子 Fiber 节点 workInProgress.child mountChildFibers( workInProgress, null, nextChildren, renderLanes, ); } else { // 对于 update 的组件执行 Diff将比较的结果生成新 Fiber 节点 workInProgress.child reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes, ); } }也就是说Diff 只发生在updatecurrent ! null场景mount时走的是mountChildFibers直接创建全新的子Fiber 节点。reconcileChildFibers与mountChildFibers逻辑基本一致唯一区别是前者会为生成的Fiber 节点带上effectTag而后者不会。结合 docs/process/doubleBuffer.md 的双缓存机制可以更完整地理解current Fiber 树对应屏幕上已显示的内容workInProgress Fiber 树在内存中构建构建完成后通过切换current指针完成DOM更新。reconcileSingleElement返回的复用的 fiber正是workInProgress Fiber 树中的节点它通过alternate属性与current fiber相连。实战四道练习题巩固复用判定请判断如下JSX 对象对应的DOM 元素是否可以复用// 习题1 更新前 divka song/div // 更新后 pka song/p // 习题2 更新前 div keyxxxka song/div // 更新后 div keyoooka song/div // 习题3 更新前 div keyxxxka song/div // 更新后 p keyoooka song/p // 习题4 更新前 div keyxxxka song/div // 更新后 div keyxxxxiao bei/div公布答案习题1不能复用。未设置key prop时默认key null所以更新前后key相同都为null但更新前type为div、更新后为ptype改变则不能复用。习题2不能复用。更新前后key改变xxx→ooo不需要再判断type直接判定不能复用。习题3不能复用。更新前后key改变xxx→ooo不需要再判断type直接判定不能复用。注意本题type也变了但判定在key这一层就已经结束。习题4可以复用。更新前后key与type都未改变判定为可复用但children由ka song变为xiao bei复用的DOM 节点的子元素仍然需要更新。总结单节点 Diff 的判定口诀单节点 DiffreconcileSingleElement的核心逻辑可以浓缩为一句话先比key再比typekey不同逐个删key同type不同全删重建key、type都相同才复用。后续你可以继续阅读 docs/diff/multi.md 学习同级多个节点Array类型的 Diff那里会引入两轮遍历并用lastPlacedIndex与existingChildrenMap 来判断节点的移动是 Diff 算法中最精髓的部分。赞分享文档前端【免费下载链接】just-react「React技术揭秘」 一本自顶向下的React源码分析书项目地址https://gitcode.com/gh_mirrors/ju/just-react点击查看免费下载相关推荐React 多节点 Diff 深入解析两轮遍历与 lastPlacedIndex 移动判定just-react 源码分析React 多节点 Diff 深入解析两轮遍历与 lastPlacedIndex 移动判定just react 源码分析 本篇文章源自开源仓库《React文档前端戴森球计划终极工厂蓝图库3000设计方案快速建造高效星际工厂戴森球计划终极工厂蓝图库3000设计方案快速建造高效星际工厂 还在为戴森球计划中复杂的工厂设计而烦恼吗FactoryBluePrints蓝图库为你提供了完游戏开发react-native-reanimated v1 节点详解and 逻辑与节点的短路求值机制react native reanimated v1 节点详解 and 逻辑与节点的短路求值机制 导读 本文深入讲解 react native reanima移动开发前端上一篇WaveTools鸣潮工具箱完全指南三步实现120FPS高帧率与画质优化下一篇Hotkey DetectiveWindows系统热键占用检测的完整解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

智能体工程化落地:从评测、安全到业务集成的最佳实践

智能体工程化落地:从评测、安全到业务集成的最佳实践

1. 本周Trending观察:智能体不再是“玩具”,是“基建”这周我照例把GitHub Trending翻了几个来回,一个很直观的感受是:智能体(Agent)相关项目的热度还在涨,但涨法变了。半年前的榜单上&#xff…

2026/10/13 17:54:15 阅读更多 →
rsuite Progress 线条进度条尺寸定制:`strokeWidth` 与 `radius` 属性深度解析

rsuite Progress 线条进度条尺寸定制:`strokeWidth` 与 `radius` 属性深度解析

前端UI组件 【免费下载链接】rsuite 🧱 A suite of React components . 项目地址: https://gitcode.com/gh_mirrors/rs/rsuite 点击查看 免费下载 导读:本文以 rsuite 官方文档中 Progress 组件「尺寸(Size)」演示片…

2026/10/13 18:24:11 阅读更多 →
GSD 工作流校验策略全解:从 content-heuristic 到 human-review 的四级自动化门控

GSD 工作流校验策略全解:从 content-heuristic 到 human-review 的四级自动化门控

人工智能AI Agent代码智能体Agent 编排CLIAI 应用 【免费下载链接】gsd-2 A powerful meta-prompting, context engineering and spec-driven development system that enables agents to work for long periods of time autonomously without losing track of the big picture…

2026/10/13 21:48:59 阅读更多 →

最新新闻

C# WinForm 调用 cmd 命令避坑指南:Process 参数、编码与封装

C# WinForm 调用 cmd 命令避坑指南:Process 参数、编码与封装

简介:这份资源面向C#桌面开发初学者与需要调用系统命令的WinForm开发者,围绕在VS2010环境下通过System.Diagnostics.Process类执行CMD命令这一实用场景展开。包内共27个文件,以8个cs源码文件为核心,配合csproj项目文件、sln解决方…

2026/10/14 15:02:35 阅读更多 →
插件系统原理与故障排查:从接口约定到插件开发实践

插件系统原理与故障排查:从接口约定到插件开发实践

1. 从一次加载失败说起:插件是怎么工作的我先说一个最近的经历。有次在配置一个持续交付流水线,启动服务后给我甩了一个failed to load plugins web boot: 2 entries did not activate的报错,附带两个插件名称。当时第一反应是"插件没生…

2026/10/14 15:02:35 阅读更多 →
前端进阶Top2:工程化体系与性能优化的实战路线

前端进阶Top2:工程化体系与性能优化的实战路线

“前端进阶之路Top2”往深里看,它并不是一个排行榜,而是一条进阶路线图。如果你已经会写页面、会用一个主流框架,但总觉得离“高级前端”还差一口气,那缺的往往不是新框架,而是两道必过的坎:横向的工程化能…

2026/10/14 15:02:35 阅读更多 →
光模块PCB为何总是反复改版?揭秘高频板的三大杀手

光模块PCB为何总是反复改版?揭秘高频板的三大杀手

在通信和数据中心领域,光模块PCB是公认的“硬骨头”。很多工程师在做25G、50G甚至100G光模块设计时,发现板子总是良率低下,甚至不得不反复改版(Re-spin)。为什么光模块PCB这么难做?因为在这个领域&#xff…

2026/10/14 15:02:35 阅读更多 →
论文降AI率实战:6款免费工具与科学流程详解

论文降AI率实战:6款免费工具与科学流程详解

1. 论文降AI率的核心痛点与解决思路又到一年毕业季,最近在实验室带本科生论文时发现,不少同学都在为论文AI率过高的问题发愁。上周刚帮学妹小张处理过一篇教育类论文,查重系统显示AIGC率高达42%,远超学校规定的15%红线。这种情况在…

2026/10/14 15:02:35 阅读更多 →
Neo4j+RAG+WebGPU:Agent工程落地的三大关键技术切片

Neo4j+RAG+WebGPU:Agent工程落地的三大关键技术切片

1. 这不是一份“新闻简报”,而是一份Agent/LLM工程现场的实时切片你点开这个标题, expecting 一份带日期的资讯汇总——但实际拿到手的,会是一张2026年9月底AI工程实践的真实快照。它不讲概念、不堆术语,只记录那些正在被真实项目…

2026/10/14 15:01:35 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →