一、什么是 React?
React 是一个用于构建用户界面的 JavaScript 库,由 Facebook 开发。它采用组件化 开发模式,通过声明式编程让 UI 开发更简单高效。
1. React 核心特点
- 组件化(Component) 把页面拆成按钮、卡片、列表等小模块,可复用、可组合。React 是组件驱动的。
- 虚拟 DOM(Virtual DOM) 不直接操作真实 DOM,先在内存对比差异,只更新变化部分,速度更快。
- JSX JSX = JavaScript + XML,它不是字符串,也不是模板语法。它是JavaScript 的语法糖。
- 声明式 你只描述"要什么结果",不关心"怎么实现"
- 单向数据流 数据只能从父组件流向子组件,不能反过来。
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 自动:
- 重新执行函数组件
- 生成新虚拟 DOM
- Diff
- 更新真实 DOM
你不需要关心更新过程。
4.4 核心公式
React 的本质就是:
UI = f(state)
state 是数据
UI 是结果
你只描述这个函数关系。
4.5 生活中的例子
4.5.1 命令式(做饭)
- 洗菜
- 切菜
- 开火
- 放油
- 炒
你控制步骤。
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 核心思想
数据只能自上而下流动,所有状态修改必须在源头发生。
🎯 共同进步
作为技术路上的同行者,我深知自己的理解可能还不够完善。
如果文章中有任何疏漏或可以改进的地方,恳请不吝指教。你的每一次反馈,都是我进步的动力。
一起加油,成为更好的开发者!