React 核心原理
React 是 Facebook(Meta)开源的 UI 库。它的核心主张只有两条:声明式与组件化。理解这两点,就理解了 React 为什么这样设计。
一、React 是什么
| 维度 | 说明 |
|---|---|
| 定位 | 只负责"视图层"的 UI 库,不是完整框架 |
| 声明式 | 你描述"界面应该长什么样",React 负责"怎么变过去" |
| 组件化 | 界面由组件树组成,组件可复用、可嵌套、可组合 |
| 跨平台 | React 只做"描述",渲染交给 react-dom / react-native |
// 命令式(jQuery 时代):手动操作每一步
const box = document.getElementById("box");
box.style.color = "red";
box.innerHTML = "已点击 " + count + " 次";
// 声明式(React):只描述"状态 → 界面"的映射
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>已点击 {count} 次</button>;
}声明式的价值:你永远不手动碰 DOM,状态变了,界面自动同步。
二、JSX 语法与编译
JSX 是 JavaScript 的语法扩展,看起来像 HTML,实际上是 React.createElement 的语法糖。
const element = <h1 className="title">Hello</h1>;等价于:
const element = React.createElement("h1", { className: "title" }, "Hello");createElement 返回一个React 元素——一个普通对象:
// React 元素(element):描述"我想要什么",不是真实 DOM
{
type: "h1",
props: { className: "title", children: "Hello" }
}| 概念 | 含义 |
|---|---|
| React 元素 | 不可变的对象,描述一次渲染的快照 |
| 组件 | 函数或类,输入 props,输出 React 元素树 |
| 真实 DOM | 浏览器渲染树,由 React 负责创建与更新 |
JSX 的编译由 Babel 完成,Vite 项目用 @vitejs/plugin-react 内置处理。JSX 里嵌入变量用花括号:
const name = "小明";
const list = ["a", "b"];
<div>
<p>你好,{name}</p>
{list.map((item) => <li key={item}>{item}</li>)}
</div>三、组件的两种形式
// 函数组件:本质是一个返回 JSX 的函数
function Greeting({ name }) {
return <p>你好,{name}</p>;
}
// 类组件:必须继承 React.Component,必须有 render 方法
class Greeting extends React.Component {
render() {
return <p>你好,{this.props.name}</p>;
}
}| 对比 | 函数组件 | 类组件 |
|---|---|---|
| 定义 | 普通函数 | ES6 class |
| 状态 | 靠 Hooks | this.state / this.setState |
| this | 没有 this | 处处是 this,需绑定方法 |
| 生命周期 | 用 useEffect 模拟 | 有专门生命周期方法 |
| 现状 | 官方推荐,主流 | 仅维护老代码 |
四、props 与 state
props 是父组件传入的参数,只读;state 是组件内部的可变状态。数据流向是单向的:自上而下(父 → 子)。
function Child({ count, onAdd }) { // props 只读
return <button onClick={onAdd}>+1,当前 {count}</button>;
}
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} onAdd={() => setCount(count + 1)} />;
}| 维度 | props | state |
|---|---|---|
| 来源 | 父组件传入 | 组件自身管理 |
| 可变性 | 只读,不可修改 | 通过 setter 修改 |
| 触发更新 | 父组件更新时 | 调用 setter 时 |
| 跨组件 | 可逐层传递(prop drilling) | 通常局部 |
注意 setState 是异步批量的:
setCount(count + 1);
setCount(count + 1); // 两次可能只生效一次,应使用函数式更新
setCount((c) => c + 1);
setCount((c) => c + 1); // 函数式更新会依次累加,结果为 +2五、组件生命周期
类组件有完整生命周期,函数组件用 Hooks 模拟。三个阶段:
| 阶段 | 类组件方法 | 函数组件等价 |
|---|---|---|
| 挂载 | componentDidMount | useEffect(fn, []) |
| 更新 | componentDidUpdate | useEffect(fn, [deps]) |
| 卸载 | componentWillUnmount | useEffect 返回的清理函数 |
useEffect(() => {
console.log("挂载时执行一次");
const timer = setInterval(() => console.log("tick"), 1000);
return () => {
console.log("卸载时清理");
clearInterval(timer);
};
}, []); // 空依赖数组 = 只在挂载时执行| useEffect 依赖写法 | 执行时机 |
|---|---|
| 不传依赖数组 | 每次渲染后都执行 |
传 [] | 仅挂载后执行一次 |
传 [a, b] | 依赖变化时才执行 |
六、Hooks 核心
6.1 useState 与 useRef
const [count, setCount] = useState(0); // 状态
const inputRef = useRef(null); // 引用,跨渲染保持不变
<input ref={inputRef} />
// inputRef.current 拿到真实 DOM;也可用 ref 保存任何可变值useRef 改变 .current 不会触发重新渲染,适合存"不需要展示的值"(定时器 id、上一次的值等)。
6.2 useMemo 与 useCallback
两者都是性能优化:缓存计算结果与缓存函数引用,避免子组件因引用变化而重复渲染。
const filtered = useMemo(
() => list.filter((x) => x.age > 18), // 依赖不变则跳过重算
[list]
);
const handleClick = useCallback(
() => doSomething(filtered), // 依赖不变则函数引用稳定
[filtered]
);| Hook | 缓存对象 | 典型用途 |
|---|---|---|
useMemo | 计算值 | 昂贵计算、派生数据 |
useCallback | 函数引用 | 传给 memo 子组件的回调 |
useRef | 任意可变值 | DOM 引用、不渲染的变量 |
6.3 useContext
解决 props 逐层传递的"钻透"问题:
const ThemeContext = createContext("light");
// 顶层提供值
<ThemeContext.Provider value={theme}>
<App />
</ThemeContext.Provider>
// 任意深层消费
const theme = useContext(ThemeContext);6.4 Hooks 规则
- 只在顶层调用 Hooks:不能在 if、for、嵌套函数中调用,保证每次渲染调用顺序一致。
- 只在 React 函数组件或自定义 Hook 中调用。
React 靠调用顺序关联每个 Hook 与状态,顺序一旦改变,状态就错乱——这是规则 1 的根本原因。
// 错误示范:条件里调用 Hook
if (visible) {
const [x] = useState(0); // 顺序不稳定,报错
}
// 正确:状态在顶层,条件在内部
const [x, setX] = useState(0);
if (visible) { setX(1); }七、虚拟 DOM 与 diff 算法
虚拟 DOM(Virtual DOM):内存中维护的一棵普通 JS 对象树,作为"真实 DOM 的蓝图"。状态更新时,React 先对比新旧两棵虚拟树(diff),算出最小变更集,再批量应用到真实 DOM。
| 对比 | 直接操作 DOM | 虚拟 DOM |
|---|---|---|
| 性能 | 每次更新全量重排 | 最小化变更,批量提交 |
| 心智 | 命令式,易错 | 声明式,只管状态 |
| 代价 | 无 | 额外的内存与 diff 计算 |
diff 算法(协调过程)的三个核心策略:
- 逐层对比:只对比同一层级的节点,不跨层比较。
- 同类型组件才复用:类型不同直接卸载重建;类型相同则更新 props。
- 列表用 key 标识:key 帮助 React 识别哪些元素发生了移动、新增、删除。
// 没有 key:只能按位置对比,插入首项会导致全部重建
// 有 key:React 按 key 精准复用,只动变化项
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>key 必须是稳定且唯一的标识(用 id 而不是数组下标,下标会导致列表重排时的错误复用)。
八、渲染流程:render 与 commit
一次更新分两个阶段:
状态更新(setState)
→ render 阶段(可中断):构建新虚拟树、执行 diff、收集副作用
→ commit 阶段(不可中断):把变更一次性应用到真实 DOM、执行 useEffect| 阶段 | 做什么 | 特点 |
|---|---|---|
| render | 调用组件函数、生成元素树、diff | 纯计算,可中断、可重复 |
| commit | 操作 DOM、触发生命周期与副作用 | 必须一次完成 |
这也是为什么组件函数必须是纯函数:render 阶段可能被反复调用,副作用(请求、订阅)必须放进 useEffect,否则会重复执行。
九、Fiber 架构
React 16 引入 Fiber 架构,把"一次性同步渲染"改为"可中断的分片渲染"。
| 对比 | 旧架构(stack reconciler) | Fiber |
|---|---|---|
| 渲染 | 同步递归,不可中断 | 拆成工作单元,可中断恢复 |
| 优先级 | 无 | 高优先级任务插队(如输入、动画) |
| 用户体验 | 长任务阻塞主线程 | 让出主线程,页面保持流畅 |
Fiber 节点是链表结构,记录了工作进度,配合**任务调度器(Scheduler)**实现:
- 时间切片(Time Slicing):每帧让出主线程给浏览器绘制。
- 优先级调度:可中断的 render 阶段,让路给紧急更新。
- 高优先级更新可以打断低优先级任务(如并发特性 Concurrent Mode 的基础)。
// Fiber 节点简化示意:链表结构 + 双缓冲(current / workInProgress)
const fiber = {
tag: 1, // 节点类型(函数组件 / 类组件 / 原生标签…)
key: null,
type: "div", // 元素类型
child: null, // 指向第一个子节点
sibling: null, // 指向下一个兄弟节点
return: null, // 指向父节点
memoizedState: null, // 组件状态 / Hooks 链表头
};十、Context 与组件通信
组件通信的几种方式与适用场景:
| 方式 | 方向 | 适用场景 |
|---|---|---|
| props | 父 → 子 | 就近传参 |
| 回调函数 | 子 → 父 | 子组件把事件上抛 |
| Context | 跨层级 | 主题、语言、用户信息 |
| 状态库 | 全局 | 复杂共享状态(Redux / Zustand) |
// Context 更新:provider 的 value 变化会通知所有消费者重新渲染
const ThemeContext = createContext("light");
function App() {
const [theme, setTheme] = useState("dark");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>工具栏</div>;
}注意:Context 的 value 每次渲染都是新对象,会触发所有消费者重渲染。需要精确更新时,可以把 provider 拆小,或用 useMemo 稳定 value。
十一、要点速查
| 概念 | 一句话记忆 |
|---|---|
| 声明式 | 描述状态与界面的映射,不手动操作 DOM |
| JSX | createElement 的语法糖 |
| props | 只读的入参,单向数据流 |
| state | 可变状态,setter 触发重新渲染 |
| Hooks | 让函数组件拥有状态与副作用 |
| 虚拟 DOM | 内存中的 UI 蓝图,diff 后最小化更新 |
| Fiber | 可中断的渲染架构 + 优先级调度 |
| Context | 跨层级共享数据 |