Day 10:原理综合与面试常问

1 阅读3分钟

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 必须同步完成
FiberFiber 解决了什么问题?主线程阻塞、无法中断恢复、优先级调度
FiberFiber 为什么用链表?可中断、可恢复,遍历更灵活
DiffReact Diff 原则?同级比较、类型不同=重建
调度调度器如何工作?时间片 + 优先级 + 过期机制
HookuseEffect 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 的工作流程?

  1. 用户触发(setState/dispatch)
  2. 创建 Update,分配 Lane(优先级)
  3. 调度器安排执行时间
  4. Render 阶段(可中断)- Diff 算法
  5. Commit 阶段(不可中断)- DOM 操作
  6. 页面渲染完成

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/缓存