--- id: frontend.react.interview title: React 面试题 type: interview domain: frontend stack: [react] tags: [React, Hooks, 渲染, 面试] level: intermediate related: [frontend.react.hooks-rendering] week: [7, 16] --- # React 面试题 ## 基础 ### 【基础】React 组件什么时候会重新渲染? **参考答案**:自身 state 变化、父组件渲染(即使 props 没变)、使用的 Context 值变化。渲染不等于 DOM 更新,React 会比较后只提交变化的部分。 **追问**:怎么避免子组件不必要的渲染?→ `memo` 包裹子组件并保证 props 引用稳定(`useMemo` / `useCallback`),或者把状态下移、把变化的部分拆成独立组件;React Compiler 可以自动处理部分记忆化。 ### 【基础】为什么 Hooks 不能写在条件语句中? **参考答案**:React 用 Hook 的调用顺序来对应每个 Hook 的状态,条件调用会导致顺序变化,状态错位。 **追问**:需要条件逻辑怎么办?→ 在 Hook 内部写条件,或者拆分组件。 ### 【基础】`useEffect` 的执行和清理时机? **参考答案**:在渲染提交到屏幕之后执行;依赖变化时,先执行上一次的清理函数,再执行新的 effect;组件卸载时执行清理。开发环境的严格模式会额外执行一次“挂载 → 卸载 → 挂载”来暴露缺少清理的问题。 **追问**:`useLayoutEffect` 有什么区别?→ 在 DOM 更新后、浏览器绘制前同步执行,用于测量布局,避免闪烁;会阻塞绘制,谨慎使用。 ## 进阶 ### 【进阶】setState 是同步还是异步的? **参考答案**:调用后不会立即改变当前渲染中的值(状态是快照),更新会被批量处理并在之后重新渲染。React 18 起在事件、Promise、定时器中都会自动批量。依赖旧值的更新用函数式写法 `setX(x => x + 1)`。 **追问**:需要立即拿到更新后的 DOM 怎么办?→ 极少数情况可以用 `flushSync`,一般在 effect 中处理。 ### 【进阶】React 和 Vue 的更新机制有什么区别? **参考答案**:Vue 通过响应式系统精确追踪依赖,数据变化时只更新依赖它的组件;React 状态变化后从该组件开始重新执行渲染函数并比较,默认会渲染子树,靠 memo 等手段优化。Vue 的 `setup` 只执行一次,React 函数组件每次渲染都执行。 **追问**:哪种更好?→ 各有取舍:Vue 自动优化、心智负担小;React 模型简单(UI = f(state))、生态和并发特性丰富。 ### 【进阶】服务端数据为什么不建议放在 Redux 里? **参考答案**:服务端数据需要缓存、失效、后台刷新、重试、去重请求、分页等能力,自己在 Redux 中实现复杂且容易出错。TanStack Query 等库专门处理这些;全局 store 只放真正的客户端状态。 **追问**:TanStack Query 的 `staleTime` 和 `gcTime` 是什么?→ `staleTime` 决定数据多久后被视为过期(过期才会重新请求);`gcTime` 决定不再使用的缓存保留多久后被回收。 ## 场景题 ### 【场景题】设计一个长任务(Agent 生成报表)的前端界面,要注意什么? **参考答案**:以服务端为准(任务 ID、刷新后恢复、幂等提交);`useReducer` 状态机;事件序号去重;重连时先取快照;失败区分可重试;取消等待后端确认;等待用户输入的交互。详见[长任务界面场景](scenarios/long-task-ui.md)。 **追问**:怎么测试?→ reducer 是纯函数,直接写单元测试覆盖各种事件序列;组件用 Testing Library 模拟事件订阅。 ### 【场景题】搜索框输入时列表过滤很卡,怎么优化? **参考答案**:输入框状态和列表过滤分离;用 `useDeferredValue` 让列表渲染延后,保证输入流畅;列表虚拟化;过滤计算 `useMemo`;数据量大时防抖或放到 Web Worker / 服务端。 **追问**:防抖和 `useDeferredValue` 的区别?→ 防抖固定延迟后才执行;`useDeferredValue` 由 React 调度,空闲时尽快渲染,可以被新的输入中断,不需要选择延迟时间。