news 2025/12/28 6:57:35

深入 React 原理:Reconciliation

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入 React 原理:Reconciliation

写在前面:从 Vue 转到 React,最大的挑战往往不是 JSX 语法,而是对"渲染机制"底层逻辑的理解偏差。今天从一个看似简单的 Bug,探讨 React 的 Reconciliation(协调)算法原理。

前排广告位:欢迎访问我的个人网站:https://hixiaohezi.com


在 React 社区的问答中,有一个现象非常普遍:“为什么我的 Input 输入框每输入一个字符就会失去焦点?”

其实是因为它触及了 React 最核心的渲染原理——组件身份(Identity)与协调(Reconciliation)

问题背景

很多从 Vue 转向 React 的开发者(包括我自己),在初期为了"方便"或者实现逻辑闭环,可能会写出类似这样的代码:

export default function UserForm() { const [name, setName] = useState(''); // 🔴 错误示范:在组件内部定义组件 // 很多开发者习惯在 Vue 的 template 中直接写局部逻辑, // 在 React 中容易误以为这样是在拆分 render 函数 const StyledInput = ({ value, onChange }) => ( <input style={{ border: '1px solid blue', padding: '10px' }} value={value} onChange={onChange} /> ); return ( <div> <h3>用户录入</h3> <StyledInput value={name} onChange={(e) => setName(e.target.value)} /> <p>当前输入:{name}</p> </div> ); }

Bug 出现

当在输入框中按下第一个字符(比如 ‘a’),name状态更新,组件重渲染。紧接着,输入框的焦点瞬间丢失。除了失去焦点,如果这个子组件里有任何内部 State,它们也会全部重置。

原理深度分析

要理解这个问题,必须深入 React 的 Diff 算法(协调过程)。React 在更新时,会比较新旧两棵虚拟 DOM 树(Fiber Tree)。

比较的一个核心策略是:类型(Type)的同一性检查

1. 不同的"类"生成不同的树

React 检查一个节点时,首先看它的type

  • 如果oldNode.type === newNode.type:React 认为这是同一个组件,它会保留该组件实例(及其 State),只更新 Props(Update)。
  • 如果oldNode.type !== newNode.type:React 认为这是两个完全不同的东西,它会**卸载(Unmount)旧组件,丢失其所有状态和 DOM 节点,然后挂载(Mount)**新组件。

2. 函数也是对象

回到上面的代码。StyledInput是在UserForm内部定义的函数。
这意味着:UserForm每次渲染时,都会创建一个全新的StyledInput函数(引用地址不同)。

渲染流程回放:

  1. 第一次渲染

    • 创建StyledInput(函数地址 A)。
    • 渲染<StyledInput />(Type 是 A)。
    • 结果:挂载组件 A。
  2. 用户输入 ‘a’ -> 触发 setName -> 触发第二次渲染

    • UserForm重新执行。
    • 创建StyledInput(函数地址 B)。注意:虽然代码逻辑一样,但在内存中A !== B
    • React 比较:旧节点的 Type 是 A,新节点的 Type 是 B。
    • 判定:Type 不同!这不是同一个组件!
    • 执行:卸载 A(移除 DOM,销毁 State),挂载 B(创建新 DOM,初始化 State)。

3.DOM 的毁灭与重生

因为组件被卸载并重新挂载,旧的<input>DOM 元素被从页面中移除,一个新的<input>被创建并插入。
物理 DOM 都换了,焦点自然就没了。

Vue 与 React 的视角差异

对于习惯 Vue 的开发者来说,这种行为可能略显反直觉:

  • Vue 的视角:组件通常是在.vue文件中静态定义的,或者在components选项中注册。组件定义的"引用"在整个应用生命周期中通常是稳定的(Static)。即使在setup()内部返回渲染函数,也不太容易犯"在渲染过程中重新定义组件类"的错误。
  • React 的视角:React Component 本质就是 JavaScript 函数。在 JS 中,函数内定义函数,等于每次执行外层函数时都新建一个闭包函数。React 的渲染完全依赖于 JS 的运行结果,因此对引用的稳定性要求极高。

解决方案

修正方案非常简单:保证组件类型(Type)的引用稳定性。

方案一:移到外部(标准解法)

将子组件定义移到父组件外面。这样StyledInput就在模块加载时被创建一次,永远不会变。

// ✅ 正确:定义在外部,引用地址恒定 const StyledInput = ({ value, onChange }) => ( <input ... /> ); export default function UserForm() { // ... return <StyledInput ... />; }

方案二:使用 render props 或直接写 JSX

如果不仅仅是为了复用,只是为了拆分 JSX 结构,可以直接拆分成函数返回(注意不是组件,是返回 Element 的函数),或者直接把 JSX 存在变量里。

总结

在 React 中,组件在 UI 树中的位置(Position)和类型(Type)共同决定了它的身份(Identity)。身份变了,状态就没了。


欢迎访问我的个人网站:

👉 hixiaohezi.com

愿我们对原理的每一次深究,都能化作代码中的一份从容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/16 20:05:10

LobeChat能否集成TTS语音合成?语音输出实现路径

LobeChat能否集成TTS语音合成&#xff1f;语音输出实现路径 在智能对话系统逐渐从“能说会写”迈向“有声有色”的今天&#xff0c;用户对交互体验的期待早已超越了纯文本的边界。尤其是在车载导航、老年陪伴、无障碍访问等场景中&#xff0c;“听懂AI的回答”比“看到AI的回答…

作者头像 李华
网站建设 2025/12/16 20:04:58

Dify平台在教育领域AI助教系统中的应用设想

Dify平台在教育领域AI助教系统中的应用设想 如今&#xff0c;越来越多的学生在课后提问&#xff1a;“这道题老师讲过&#xff0c;但我还是不会。”而教师也常面临“同一个问题被反复问十几次”的困境。这种信息不对称与教学资源有限的矛盾&#xff0c;在大班制教育中尤为突出…

作者头像 李华
网站建设 2025/12/27 7:48:10

生物信息分析高手私藏代码(R语言代谢组完整流程大公开)

第一章&#xff1a;R语言代谢组学分析概述R语言作为统计计算与数据可视化的强大工具&#xff0c;在生物信息学领域尤其是代谢组学分析中发挥着核心作用。其丰富的扩展包生态系统支持从原始数据预处理到多元统计建模、通路富集分析及高质量图形输出的全流程操作&#xff0c;极大…

作者头像 李华
网站建设 2025/12/16 20:03:09

平台与独立站双轨并行:跨境电商多元化渠道布局与风险对冲策略

在跨境电商 “渠道依赖风险加剧、流量成本高企” 的当下&#xff0c;“单靠平台打天下” 或 “孤注一掷做独立站” 的模式均难以为继。平台与独立站双轨并行&#xff0c;既是 “快速起量” 与 “长期建牌” 的平衡&#xff0c;更是 “分散风险” 与 “提升抗周期能力” 的核心解…

作者头像 李华
网站建设 2025/12/16 20:02:02

为什么你的回归分析总出错?:临床数据中因果推断的R语言正解

第一章&#xff1a;为什么你的回归分析总出错&#xff1f;&#xff1a;临床数据中因果推断的R语言正解 在临床研究中&#xff0c;回归分析常被用于探索变量之间的关系&#xff0c;但许多分析结果却因混淆偏倚、模型误设或忽略因果结构而产生误导。关键问题在于&#xff1a;传统…

作者头像 李华
网站建设 2025/12/27 9:10:46

构建可扩展量子模拟器的R语言秘籍(仅限高级开发者)

第一章&#xff1a;多qubit量子模拟的R语言架构设计 在构建多qubit量子系统模拟器时&#xff0c;R语言凭借其强大的矩阵运算能力和可扩展的函数式编程范式&#xff0c;成为实现量子态演化与测量的有效工具。设计一个模块化的架构&#xff0c;能够清晰分离量子态初始化、门操作应…

作者头像 李华