React 面试笔记:虚拟 DOM、声明式渲染与副作用管理
这篇笔记是我结合自己学习和问答时的思考整理的,记录我从“听不懂”到“能表达”的过程。
比起死记硬背八股文,我更希望把当时的思考逻辑写清楚:我为什么这样理解,为什么 React 要这样设计。
一、从公式开始:UI = f(state)
我当时最先接触的是这句经典公式:
UI = f(state)
React 的核心哲学其实就藏在这句话里——UI 是由状态(state)决定的纯函数映射结果。
换句话说:
- state:组件的内部状态,是驱动 UI 变化的唯一事实来源;
- f(函数组件) :根据 state(和 props)返回虚拟 DOM 的描述;
- UI:不是浏览器看到的真实 DOM,而是 React 内部的一份「轻量级描述」。
我最开始的理解是: UI 的一切变化都来自 state。
Hooks(比如 useEffect、useContext)虽然能影响 UI,但它们的底层逻辑最终也都是围绕 state 的变化展开。
二、声明式 vs 命令式:为什么声明式更抽离?
当时我问自己:
“React 为什么强调声明式?以前用 jQuery 不也挺灵活吗?”
然后我发现两者的差别在于抽象层级不同。
命令式(如 jQuery):
每一步都得手动写:
document.querySelector('.btn').classList.add('active');
声明式(React):
只管结果:
<button className={isActive ? 'active' : ''}>点击我</button>
我记得自己当时是这么想的:
React 的声明式更像 Vue 的组合式 API,只描述「状态是什么样就显示什么样」,而不是「怎么去改成那样」。
所以声明式编程更抽离。它让 UI 和逻辑的关系变得纯粹、直接,也带来了——
三、可预测性:一切从 state 出发
我写 React 的时候,最常用的调试手段就是打开 React DevTools 看 state。
因为在 React 里:
给定相同的 state,UI 永远是一致的。
出问题时,我会按这个逻辑排查:
- 看 state 是否是预期值;
- 看 props 是否传对;
- 对照 UI 节点,找出不一致的部分。
所以我后来体会到:
React 的可预测性不只是理念,而是实际开发中最管用的定位方法。
出 bug 时,不用去查 DOM,而是查「状态 → 渲染」的映射链。
四、关注点分离:逻辑与 UI 的分层
当时我写的总结是:
“关注点分离就是不再关注底层 DOM API,只管理业务逻辑和状态。”
以前我用原生 JS,要管 DOM 更新;
现在用 React,我只管数据,UI 是它的自然映射。
这样带来的好处我当时列了三条:
- 逻辑和 UI 分层,更清晰;
- UI 改动不会影响业务逻辑;
- 纯函数组件可以单测,更容易测试。
五、副作用管理:为什么要用 useEffect?
我当时有个很大的困惑:
“既然 JSX 已经渲染了,那直接改
document.body不就行了吗?”
后来我明白:
- JSX 只是虚拟 DOM;
- useEffect 才是处理真实 DOM 操作、副作用、异步任务的地方。
例子:
const [theme, setTheme] = useState('light');
useEffect(() => {
document.body.className = theme;
return () => { document.body.className = ''; };
}, [theme]);
return <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>切换主题</button>;
我的理解是:
- useState 管数据;
- useEffect 管副作用;
- 渲染函数本身应该保持纯净,不能直接改 DOM。
否则 React 的 diff、批量更新、甚至 SSR 都会出问题。
一句话总结:
JSX 描述虚拟 DOM,useEffect 更新真实 DOM。
六、虚拟 DOM 与真实 DOM
我一开始也以为 JSX 渲染出来的就是 DOM,后来才理清:
| 概念 | 描述 |
|---|---|
| 虚拟 DOM | React 内存中的 UI 描述,用来 diff |
| 真实 DOM | 浏览器实际显示的内容 |
| useEffect | 操作真实 DOM 或执行副作用的入口 |
React DevTools 里能看到的是虚拟 DOM 的状态,而最终渲染的是真实 DOM。
所以 React 先「算出差异」(diff),再「批量更新真实 DOM」。
七、状态更新的链路(以 Counter 为例)
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>+1 ({count})</button>;
}
流程:
- 用户点击按钮;
- 触发 onClick;
- 调用 setCount;
- React 检测 state 改变;
- 重新执行组件函数;
- 生成新虚拟 DOM;
- diff 比较差异;
- 更新真实 DOM。
我当时记住的是一句话:
“React 的工作就是让状态变化和 UI 更新之间的这条链路自动化。”
八、全局状态管理(Redux 对比 Pinia)
我对 Redux 的理解是从 Vue 的 Pinia 类比开始的:
- store 就像一个全局仓库;
- 不能直接改数据,要通过 action(“操作说明书”);
- reducer 是总管,收到 action 后返回新 state;
- 组件通过 useSelector 订阅状态;
- 一旦状态变化,组件会自动重新渲染。
我当时写的记忆口诀:
dispatch = 派发命令
action = 操作说明书(type + payload)
reducer = 更新规则
useSelector = 订阅状态
九、开发前的“三个灵魂问题”
我后来总结出 React 组件开发的三个关键自检问题:
1️⃣ State:最小完备状态是什么?
哪些数据会随时间或操作变化?由谁管理?如何保持单一数据源?
2️⃣ f(组件函数):渲染逻辑是否纯净?
是否只根据 state/props 渲染?有没有副作用?
3️⃣ 事件与状态变更:
哪些交互触发状态更新?事件如何调用 setState?新 UI 如何映射回来?
这三个问题,其实就是 React 哲学的三根支柱。
十、总结
React 的核心思维:
UI = f(state)
这不仅是一句公式,而是整个框架的设计哲学。
- 声明式:只关心结果,不管过程
- 可预测性:同一 state 对应同一 UI
- 关注点分离:逻辑与渲染分层
- 副作用管理:用 useEffect 管理真实 DOM 与异步行为
- 虚拟 DOM:通过 diffing 实现高效更新
一句话回顾我当时的理解:
React 不是在帮我“写界面”,而是在帮我“管理变化”。