React 核心原理完全解析:从组件化、虚拟DOM到声明式编程

9 阅读11分钟

一、什么是 React?

React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发。它采用组件化开发模式,通过声明式编程让 UI 开发更简单高效。

1. React 核心特点

  1. 组件化(Component) 把页面拆成按钮、卡片、列表等小模块,可复用、可组合。React 是组件驱动的。
  2. 虚拟 DOM(Virtual DOM) 不直接操作真实 DOM,先在内存对比差异,只更新变化部分,速度更快
  3. JSX JSX = JavaScript + XML,它不是字符串,也不是模板语法。它是JavaScript 的语法糖。
  4. 声明式 你只描述“要什么结果”,不关心“怎么实现”
  5. 单向数据流 数据只能从父组件流向子组件,不能反过来。

1. 组件化

1.1 组件化是什么?

组件化是将页面拆分为独立、可复用、可组合的代码单元的开发模式。每个组件负责渲染 UI 的特定部分,并管理自己的状态和行为。

React组件化思维

传统开发:一个页面 = 一个 HTML 文件
组件化开发:一个页面 = 多个组件的组合

[App 根组件]

┌────┴────┐
[Header]  [Content]
↓          ↓
[Logo]   [Sidebar] [Main]

1.2 为什么需要组件化?

传统开发的痛点

<!-- 2000 行代码的单个 HTML -->
<div id="app">
  <!-- 头部 -->
  <div class="header">...</div>
  <!-- 侧边栏 -->
  <div class="sidebar">...</div>
  <!-- 内容区 -->
  <div class="content">
    <!-- 混杂着 JS 逻辑 -->
    <script>
      // 代码难以维护
    </script>
  </div>
  <!-- 更多代码... -->
</div>

问题:

  • 代码耦合严重,牵一发而动全身
  • 难以复用,只能复制粘贴
  • 团队协作困难,容易冲突
  • 测试困难,必须测试整个页面
1.3 组件化的优势

高内聚低耦合 - 组件内部紧密相关,组件间相互独立
可复用性 - 一次编写,多处使用
可维护性 - 每个组件独立维护,职责单一
可测试性 - 可以单独测试每个组件
团队协作 - 并行开发不同组件

函数组件(主流)

function App() {
  return <div>Hello</div>;
}

类组件(旧写法)

class App extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

现在基本都用函数组件 + Hooks。

2.虚拟DOM

要理解虚拟 DOM,最直接的方式是回答三个问题:它是什么?它解决了什么问题?它是怎么做的?

2.1 什么是虚拟DOM?

虚拟 DOM(Virtual DOM)本质就是:用 JavaScript 对象来描述真实 DOM

真实DOM:

<div id="app">
  <h1>Hello</h1>
</div>

在 React 里会变成类似这样的 JS 对象:

{
  type: 'div',
  props: {
    id: 'app',
    children: [
      {
        type: 'h1',
        props: {
          children: 'Hello'
        }
      }
    ]
  }
}

它不是浏览器的真实节点,只是一个描述结构的 JS 对象。

2.2 为什么需要虚拟 DOM?

核心原因只有一个:

直接操作真实 DOM 性能开销大

真实 DOM 操作很慢,因为:

  • DOM 是浏览器 API
  • DOM 操作会引起重排(reflow)
  • 会触发布局和绘制

React 解决方案:

👉 先在内存中计算出变化
👉 再一次性更新真实 DOM

这就是虚拟 DOM 的意义。

2.3 React 更新流程(重点)

初次当你调用的时候

setState()

1. 生成虚拟DOM

render() -> new Virtual DOM

2. 和旧的虚拟 DOM 做对比(Diff 算法)

React会比较

oldVDOM vs newVDOM

找出差异。

3. 生成最小更新补丁(Patch)

例如:

  • 文本变了 → 只改文本
  • class 变了 → 只改 class
  • 子节点变了 → 局部替换
4. 批量更新真实 DOM

React 会统一执行最少的 DOM 操作。

总结

第一步:初次渲染

  • 组件返回 JSX,React 将其转为虚拟 DOM 树。
  • React 根据虚拟 DOM 树创建真实 DOM,插入页面。

第二步:状态更新

  • 状态变化,组件重新执行,生成新的虚拟 DOM 树
  • React 把新树旧树传给 Diffing 算法进行比较。

第三步:计算差异并批量更新

  • Diff 算法找出两棵树的最小差异。
  • React 把这些差异批量更新到真实 DOM 上,只操作必要节点。

关键洞察:虚拟 DOM 快的不是“比较”这个过程,而是它让开发者能用声明式的写法,同时通过批量更新避免了频繁操作 DOM

2.4 React Diff 算法原理(面试必问)

React 的 Diff 有 3 个核心假设:

2.4.1 同层比较(不会跨层比较)

只比较

div
 ├─ p
 └─ span

不会把 p 和 div 比。

这样把复杂度从:

O(n³)

降到

O(n)

2.4.2 不同类型直接替换
 

React 直接销毁重建。

2.4.3 key 是优化列表更新的关键

错误写法:

{list.map((item, index) =>
  <li key={index}>{item}</li>
)}

正确写法

{list.map((item) =>
  <li key={item.id}>{item.name}</li>
)}

因为:

   key 用来判断节点是否“同一个”

否则 React 会误判,导致:

  • 组件状态错乱
  • 性能变差
2.5 虚拟 DOM ≠ 性能一定更快

很多人误解:

React 比原生 DOM 快,因为虚拟 DOM

❌ 不完全对

真实情况是:

  • 少量 DOM 操作 → 原生更快
  • 复杂 UI 更新 → React 更优

虚拟 DOM优势在:

  • 批量更新
  • 可预测更新
  • 组件化管理
2.6 虚拟 DOM 的本质一句话

虚拟 DOM 是一个“状态到 UI 的映射缓存层”

UI = f(state)

React 通过虚拟 DOM保证:

state 改变 → 自动算出最小 DOM 更新

2.7 简单对比
方案更新方式性能
原生 DOM手动操作容易频繁重排
jQuery直接操作 DOM逻辑复杂
React状态驱动 + 虚拟 DOM自动最小更新
2.8 面试标准回答模板

如果面试问:

什么是虚拟 DOM?

你可以回答:

虚拟 DOM 是 React 用 JavaScript 对象表示真实 DOM 的一种机制。每次状态更新时,React 会生成新的虚拟 DOM,与旧虚拟 DOM 进行 Diff 算法比较,计算出最小的 DOM 变更,然后批量更新真实 DOM,从而提升性能和开发效率。

3.JSX语法

官方定义:JSX 是 JavaScript 的语法扩展,它类似于模板语言,但具有 JavaScript 的全部能力

本质:JSX 不是模板引擎,不是 HTML,它是 React.createElement 的语法糖。

function App() {
  return <h1>Hello React</h1>
}

JSX 是 JavaScript 的语法扩展,本质会被编译成:

React.createElement(type, props, children)

例如:

const element = <h1>Hello</h1>

会变成

const element = React.createElement("h1", null, "Hello");

本质:JSX 只是语法糖。

3.1 JSX 必须有一个根节点

❌ 错误:

return (
  <h1>Hello</h1>
  <p>World</p>
);

✅ 正确:

return (
  <div>
    <h1>Hello</h1>
    <p>World</p>
  </div>
);

或者使用 Fragment:

return (
  <>
    <h1>Hello</h1>
    <p>World</p>
  </>
);

3.2 JSX 中如何写 JS 表达式?

{}

const name = "Jake";

<h1>Hello {name}</h1>

也可以写表达式:

<h1>{1 + 2}</h1>
<h1>{isLogin ? "已登录" : "未登录"}</h1>

⚠️ 只能写表达式,不能写语句:

❌ 错误:

{ if (true) { ... } }

✅ 正确:

{ condition && <div>显示</div> }

3.3 JSX 中的属性写法
3.3.1 class 要写成 className
<div className="box"></div>

3.3.2 for 要写成 htmlFor
<label htmlFor="name">姓名</label>

因为 for 是 JS 关键字。

3.3.3 事件使用驼峰
<button onClick={handleClick}></button>

不是:

onclick

3.3.4 JSX 中写样式
<div style={{ color: "red", fontSize: "20px" }}>

注意:

  • 外层 {} 是 JS
  • 内层 {} 是对象
3.3.5 JSX 渲染列表
const list = [1,2,3];

<ul>
  {list.map(item =>
    <li key={item}>{item}</li>
  )}
</ul>

必须有 key。

3.3.6 JSX 条件渲染

三元表达式

{isLogin ? <Home /> : <Login />}

逻辑与

{isShow && <div>显示</div>}

3.3.7 JSX 组件写法

函数组件:

function Hello(props) {
  return <h1>Hello {props.name}</h1>;
}

使用:

<Hello name="Jake" />

3.4 总结(面试标准回答)

如果问:

JSX 是什么?

你可以回答:

JSX 是 JavaScript 的语法扩展,本质是 React.createElement 的语法糖。它允许我们用类似 HTML 的写法描述 UI,最终会被 Babel 编译成 JavaScript 对象,也就是虚拟 DOM。

3.5 常见的易错点
  • JSX 必须有根节点
  • 不能写 if 语句
  • 必须写 key
  • className 而不是 class
  • style 是对象

4.声明式

4.1 什么是声明式

声明式编程关注的是"做什么"(what),而不是"怎么做"(how)。你只需要描述你想要的UI状态,React会自动处理DOM更新。

4.2 命令式 vs 声明式
4.2.1 命令式(Imperative)

你一步一步告诉程序怎么做。

例如操作 DOM:

const btn = document.getElementById("btn");
btn.addEventListener("click", function() {
  const box = document.getElementById("box");
  box.style.display = "none";
});

特点:

  • 手动找 DOM
  • 手动改样式
  • 手动控制流程

👉 你在控制“过程”

4.2.2 声明式(Declarative)

你只告诉它:

当状态变成什么样,UI 应该是什么样

React 写法:

function App() {
  const [show, setShow] = React.useState(true);

  return (
    <>
      <button onClick={() => setShow(false)}>隐藏</button>
      {show && <div id="box">内容</div>}
    </>
  );
}

你没有:

  • 手动 getElementById
  • 手动修改 style
  • 手动删除节点

你只是声明

UI = f(state)

4.3 React 为什么是声明式?

因为:

React 只关心 state → UI 的映射关系

当 state 改变:

setShow(false)

React 自动:

  1. 重新执行函数组件
  2. 生成新虚拟 DOM
  3. Diff
  4. 更新真实 DOM

你不需要关心更新过程。

4.4 核心公式

React 的本质就是:

UI = f(state)

state 是数据
UI 是结果

你只描述这个函数关系。

4.5 生活中的例子
4.5.1 命令式(做饭)
  1. 洗菜
  2. 切菜
  3. 开火
  4. 放油

你控制步骤。

4.5.2 声明式(点外卖)

   你只说

我要一份牛肉面

怎么做你不关心。

4.6 代码中的例子
4.6.1 命令式循环
const arr = [1,2,3];
const result = [];

for(let i = 0; i < arr.length; i++){
  result.push(arr[i] * 2);
}

4.6.2 声明式
const result = arr.map(item => item * 2);

你没有说怎么循环,只声明转换规则。

4.7 声明式的优缺点
优点

1️⃣ 代码更简洁
2️⃣ 更易维护
3️⃣ 状态可预测
4️⃣ 更适合复杂 UI

缺点

1️⃣ 抽象层高
2️⃣ 不易调试底层
3️⃣ 性能优化要理解内部机制

4.8 总结(面试版)

如果问:

什么是声明式编程?

可以这样答:

声明式编程是一种只描述结果而不关心实现过程的编程方式。在 React 中,我们通过描述 state 和 UI 的映射关系,让框架自动处理 DOM 更新逻辑。

5.单向数据流

5.1 什么是单向数据流?

单向数据流(Unidirectional Data Flow)是指:数据在应用中只有一个方向流动——从父组件流向子组件,数据的变化只能通过特定的方式触发,不能直接修改祖先组件的数据。

核心公式

数据(State)→ 视图(UI)→ 行为(Action)→ 新数据(New State)→ 新视图(New UI)

数据永远是单向的,没有环路。

5.2 React例子

父组件

function Parent() {
  const [count, setCount] = React.useState(0);

  return (
    <>
      <h1>{count}</h1>
      <Child count={count} />
    </>
  );
}

子组件

function Child(props) {
  return <div>{props.count}</div>;
}

这里:

count 从 Parent 传给 Child

这就是单向数据流。

5.3 子组件能不能改父组件数据?

❌ 不能直接改

错误:

props.count = 100

React 不允许。

✅ 正确方式:父组件把“修改函数”传下去

父组件 - 银行(掌握钱)

function Bank() {
  const [money, setMoney] = useState(1000);

  return (
    <div>
      <h2>银行总资产:¥{money}</h2>
      {/* 数据向下流:银行给ATM机钱,但ATM机不能直接改银行余额 */}
      <ATM 
        currentMoney={money}      // ✅ 数据向下银行ATM
        withdraw={setMoney}       // ✅ 回调向下银行给ATM取款权限
      />
    </div>
  );
}

子组件 - ATM机(只能取钱,不能直接改银行余额)

function ATM({ currentMoney, withdraw }) {
  return (
    <button onClick={() => withdraw(currentMoney - 100)}>
      💰 取100元(当前余额:¥{currentMoney})
    </button>
  );
}

单向数据流路线图:

银行(数据所有者)
↓ 把钱给ATM机(props)
↓ ATM机显示余额(只读)
↓ 用户点取款(事件)
↓ ATM机向银行发信号(回调)
↓ 银行自己改余额(setState)
↓ 银行重新给ATM机新余额(props)
↓ 界面更新

关键区别:

  • ✅ 你的例子:父传了 setCount,子直接调用——这是允许的,但不够直观
  • ✅ 这个例子:父既传数据(只读)、又传修改方法(回调),清楚看到数据从哪里来、修改权限在哪里

本质一句话:

钱在银行手里,ATM机只能请求取钱,不能自己打开金库改数字。

5.4 为什么要单向数据流?

如果是双向数据流会发生什么?

比如 A 改 B
B 又改 C
C 又改 A

你会发现:

  • 数据来源变得不可追踪
  • 状态错乱
  • 难以维护

单向数据流的优点:

  • 数据来源清晰
  • 状态可预测
  • 更容易调试
  • 更适合大型应用
5.5 单向数据流 + 声明式

React 的核心公式:

UI = f(state)

单向数据流保证:

state 改变 → UI 自动更新

而不是:

UI 改变 → 影响 state → 再影响别的 UI

5.6 和 Vue 的区别

Vue 2 有双向绑定:

<input v-model="msg">

这属于:

 双向数据绑定(本质还是单向数据流 + 语法糖)

React 则强调:

  • 数据只往下流
  • 表单也是受控组件
5.7 总结(面试回答)

如果问:

什么是单向数据流?

可以这样回答:

单向数据流指数据只能从父组件流向子组件,子组件不能直接修改父组件的数据。当数据发生变化时,会重新渲染 UI,从而保证状态来源清晰、可预测,便于维护和调试。

5.8 核心思想

数据只能自上而下流动,所有状态修改必须在源头发生。

🎯 共同进步
作为技术路上的同行者,我深知自己的理解可能还不够完善。
如果文章中有任何疏漏或可以改进的地方,恳请不吝指教。你的每一次反馈,都是我进步的动力。
一起加油,成为更好的开发者!