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

一、什么是 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 为什么需要组件化?

传统开发的痛点

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

问题:

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

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

函数组件(主流)

javascript 复制代码
function App() {
  return <div>Hello</div>;
}

类组件(旧写法)

scala 复制代码
class App extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

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

2.虚拟DOM

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

2.1 什么是虚拟DOM?

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

真实DOM:

xml 复制代码
<div id="app">
  <h1>Hello</h1>
</div>

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

css 复制代码
{
  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 更新流程(重点)

初次当你调用的时候

scss 复制代码
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 同层比较(不会跨层比较)

只比较

css 复制代码
div
 ├─ p
 └─ span

不会把 p 和 div 比。

这样把复杂度从:

O(n³)

降到

O(n)

2.4.2 不同类型直接替换

React 直接销毁重建。

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

错误写法:

javascript 复制代码
{list.map((item, index) =>
  <li key={index}>{item}</li>
)}

正确写法

javascript 复制代码
{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 的语法糖。

javascript 复制代码
function App() {
  return <h1>Hello React</h1>
}

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

bash 复制代码
React.createElement(type, props, children)

例如:

javascript 复制代码
const element = <h1>Hello</h1>

会变成

ini 复制代码
const element = React.createElement("h1", null, "Hello");

本质:JSX 只是语法糖。

3.1 JSX 必须有一个根节点

❌ 错误:

javascript 复制代码
return (
  <h1>Hello</h1>
  <p>World</p>
);

✅ 正确:

javascript 复制代码
return (
  <div>
    <h1>Hello</h1>
    <p>World</p>
  </div>
);

或者使用 Fragment:

javascript 复制代码
return (
  <>
    <h1>Hello</h1>
    <p>World</p>
  </>
);
3.2 JSX 中如何写 JS 表达式?

{}

ini 复制代码
const name = "Jake";

<h1>Hello {name}</h1>

也可以写表达式:

css 复制代码
<h1>{1 + 2}</h1>
<h1>{isLogin ? "已登录" : "未登录"}</h1>

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

❌ 错误:

arduino 复制代码
{ if (true) { ... } }

✅ 正确:

css 复制代码
{ condition && <div>显示</div> }
3.3 JSX 中的属性写法
3.3.1 class 要写成 className
ini 复制代码
<div className="box"></div>
3.3.2 for 要写成 htmlFor
ini 复制代码
<label htmlFor="name">姓名</label>

因为 for 是 JS 关键字。

3.3.3 事件使用驼峰
xml 复制代码
<button onClick={handleClick}></button>

不是:

onclick

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

注意:

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

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

必须有 key。

3.3.6 JSX 条件渲染

三元表达式

javascript 复制代码
{isLogin ? <Home /> : <Login />}

逻辑与

css 复制代码
{isShow && <div>显示</div>}
3.3.7 JSX 组件写法

函数组件:

javascript 复制代码
function Hello(props) {
  return <h1>Hello {props.name}</h1>;
}

使用:

ini 复制代码
<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:

ini 复制代码
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 写法:

javascript 复制代码
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 命令式循环
ini 复制代码
const arr = [1,2,3];
const result = [];

for(let i = 0; i < arr.length; i++){
  result.push(arr[i] * 2);
}
4.6.2 声明式
ini 复制代码
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例子

父组件

javascript 复制代码
function Parent() {
  const [count, setCount] = React.useState(0);

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

子组件

javascript 复制代码
function Child(props) {
  return <div>{props.count}</div>;
}

这里:

count 从 Parent 传给 Child

这就是单向数据流。

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

❌ 不能直接改

错误:

ini 复制代码
props.count = 100

React 不允许。

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

父组件 - 银行(掌握钱)

javascript 复制代码
function Bank() {
  const [money, setMoney] = useState(1000);

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

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

javascript 复制代码
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 有双向绑定:

ini 复制代码
<input v-model="msg">

这属于:

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

React 则强调:

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

如果问:

什么是单向数据流?

可以这样回答:

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

5.8 核心思想

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

🎯 共同进步

作为技术路上的同行者,我深知自己的理解可能还不够完善。

如果文章中有任何疏漏或可以改进的地方,恳请不吝指教。你的每一次反馈,都是我进步的动力。

一起加油,成为更好的开发者!

相关推荐
BD11 小时前
Umi 项目核心库升级踩坑(Umi 3→4、React 16→18、Antd 3→4、涉及 Qiankun、MicroApp 微前端)
前端·react.js
光影少年14 小时前
react中的filble架构和diffes算法如何实现的
前端·react.js·掘金·金石计划
青青家的小灰灰17 小时前
React性能优化三剑客:useEffect、useMemo与useCallback实战手册
前端·react.js
Java陈序员19 小时前
酷监控!一款高颜值的监控工具!
react.js·docker
随逸17719 小时前
《React Props 实战避坑:新手必看的组件通信指南》
react.js
码云之上2 天前
React Rnd实现自由拖动与边界检测
前端·react.js
加个鸡腿儿2 天前
React Hooks 在 Table Column Render 回调中的使用陷阱
前端·react.js·面试
小蜜蜂dry2 天前
React - useState 进阶
前端·react.js
空白诗2 天前
React Native 鸿蒙跨平台开发:@react-native-masked-view-masked-view 遮罩视图代码指南
react native·react.js·harmonyos