Day 10:原理综合与面试常问
核心概念
把之前所有知识串起来,形成完整图景!
React 工作流程全景
用户操作(点击/输入)
↓
调度器分配优先级
↓
创建 Update(更新任务)
↓
Fiber 协调(Render 阶段,可中断)
- beginWork:从根遍历
- diffFiber:对比差异
- completeWork:完成分支
↓
Commit 阶段(不可中断)
- DOM 实际操作
- 布局 effect
- useEffect 触发
↓
页面更新完成
完整工作流程详解
1. setState / dispatchAction
- 创建 Update 对象
- 分配优先级 Lane
2. scheduleUpdateOnFiber
- 加入调度队列
- requestIdleCallback / MessageChannel
3. Render 阶段(可中断)
- beginWork: 自上而下遍历 Fiber
- reconcileChildren: Diff 算法
- completeUnitOfWork: 收集 effect
4. Commit 阶段(不可中断)
- BeforeMutation: 读取 DOM
- Mutation: 删除/插入/更新 DOM
- Layout: useLayoutEffect、ref
- Passive: useEffect 异步触发
面试高频问题汇总
| 分类 | 问题 | 答案要点 |
|---|---|---|
| 基础 | React 虚拟 DOM 是什么? | JavaScript 对象,表示真实 DOM |
| 基础 | React 为什么快? | 虚拟 DOM + 高效 Diff + 批量更新 |
| 原理 | setState 后发生了什么? | 创建 Update → 调度 → Render → Commit |
| 原理 | Render 和 Commit 的区别? | Render 可中断,Commit 必须同步完成 |
| Fiber | Fiber 解决了什么问题? | 主线程阻塞、无法中断恢复、优先级调度 |
| Fiber | Fiber 为什么用链表? | 可中断、可恢复,遍历更灵活 |
| Diff | React Diff 原则? | 同级比较、类型不同=重建 |
| 调度 | 调度器如何工作? | 时间片 + 优先级 + 过期机制 |
| Hook | useEffect vs useLayoutEffect? | 异步 vs 同步 |
Hooks 常见问题
useEffect 依赖空数组 vs 不传依赖?
// 只运行一次(类似 componentDidMount)
useEffect(() => {
init();
}, []); // ✅ 依赖空数组
// 每次渲染后运行(类似 componentDidUpdate)
useEffect(() => {
update();
}); // ❌ 没传依赖,每次都运行
useEffect 里面可以 setState 吗?
// ⚠️ 可以,但要注意死循环!
useEffect(() => {
if (condition) {
setState(newValue); // 需要正确依赖
}
}, [condition]);
// 正确:加 cleanup 防止内存泄漏
useEffect(() => {
let cancelled = false;
fetchData().then(data => {
if (!cancelled) setData(data);
});
return () => { cancelled = true; };
}, []);
useState 为什么不能用在条件里?
if (condition) {
const [state, setState] = useState(0); // ❌ 错误!
}
// Hooks 必须按顺序调用,条件会导致顺序错乱
最佳实践
状态分开 vs 嵌套
// ✅ 正确:状态分开
const [name, setName] = useState('');
const [age, setAge] = useState(0);
// ❌ 错误:状态嵌套
const [user, setUser] = useState({ name: '', age: 0 });
useMemo 正确用法
// ✅ 正确:缓存计算
const expensive = useMemo(() => compute(a, b), [a, b]);
// ❌ 错误:useMemo 里面调用 setState
useMemo(() => {
setCount(c => c + 1); // ❌ 副作用!
}, []);
useCallback 配合 memo
// 父组件
function Parent() {
const handleClick = useCallback(() => {
doSomething();
}, [dependency]);
return <Child onClick={handleClick} />;
}
// 子组件
const Child = memo(function Child({ onClick }) {
return <button onClick={onClick}>Click</button>;
});
性能优化 Checklist
- 大列表用 react-window 虚拟化
- 给 list 加唯一的 key(不要用 index)
- 复杂计算用 useMemo
- 传递给子组件的函数用 useCallback
- 渲染成本高的组件用 React.memo
- 不变的组件/值移到组件外
- 动态 import 分割代码
- 图片懒加载
- 代码压缩和 Tree Shaking
自测答案
1. 完整描述 React 的工作流程?
- 用户触发(setState/dispatch)
- 创建 Update,分配 Lane(优先级)
- 调度器安排执行时间
- Render 阶段(可中断)- Diff 算法
- Commit 阶段(不可中断)- DOM 操作
- 页面渲染完成
2. 为什么 useEffect 里的 setState 可能导致死循环?
如果 useEffect 依赖不正确或 fetch 内部有 setState,会导致:
- fetch → setState → render → fetch → ...(无限循环)
正确做法:加 cleanup 或正确的依赖数组
3. React.memo、useMemo、useCallback 怎么配合?
- useMemo:缓存计算结果
- useCallback:缓存函数(给子组件用)
- memo:子组件只在 props 变化时重渲染
4. 页面卡顿的优化思路?
- 减少渲染:memo/useMemo/useCallback
- 减少计算:虚拟列表/懒加载
- 减少 DOM:简化结构
- 网络:图片压缩/CDN/缓存