React 面试题:从渲染模型到工程实践
本文目录
本文保留历史归档日期,题目按现代 React 整理。面试时不要只背 API 名称;先说明数据从哪里来、什么时候更新、边界在哪里,再用项目场景验证。
基础模型
1. React、组件和 JSX 分别是什么?
React 是构建用户界面的 JavaScript 库;组件把 UI 拆成可组合的单元;JSX 是可选的 JavaScript 语法扩展,用类似标签的方式描述界面,最终由构建工具转换。React 不等于 JSX,也不等于浏览器的 HTML。
2. Render 和 Commit 有什么区别?
Render 阶段计算本次 UI 应该是什么样,组件应保持纯净,同一输入应产生相同描述;React 可以重复、暂停或放弃某次 render。Commit 阶段才把必要变更应用到宿主环境。不要在组件 render 期间发请求、订阅或手动改 DOM。
3. props 和 state 有什么区别?
props 是组件收到的输入,组件不应直接修改;state 是组件树中某处拥有的交互数据。状态更新会安排新的 render,不会改写当前 render 已捕获的变量。数据通常向下传,事件回调把用户意图传回状态所有者。
4. 为什么依赖旧 state 更新时要用函数式 updater?
事件处理器看到的是当前 render 的状态快照。多次调用 setCount(count + 1) 会重复使用同一个 count;setCount(current => current + 1) 则让 React 按更新队列逐项计算。不要把 setter 当成同步修改当前闭包变量的函数。
5. 为什么 state 中的对象和数组要不可变更新?
原地修改会让新旧状态共享同一引用,破坏比较、调试和变更追踪。创建新对象或数组,并沿嵌套路径复制;对象展开只做浅拷贝,不会自动复制更深层的对象。不要为了更新一个可推导字段复制整份 props 到 state。
6. 列表 key 解决什么问题?
key 是同级列表项的稳定身份,帮助 React 在插入、删除和重排时对应前后元素。使用业务实体 ID;数组下标在列表会重排、过滤或插入时可能把状态错配到另一个实体。key 不会作为普通 prop 自动传给组件。
Hooks 与副作用
7. Hooks 的调用规则是什么?
只在函数组件或自定义 Hook 的顶层调用 Hooks,不要放在条件、循环、嵌套函数或普通工具函数中。这样 React 才能在每次 render 中按稳定顺序关联 Hook 状态。用官方 ESLint 插件尽早检查规则和依赖。
8. useEffect 应该解决什么问题?
Effect 用于把已提交的 React 状态与外部系统同步,例如订阅、计时器、浏览器 API 或第三方控件。纯计算结果直接在 render 中得出;由点击触发的动作放在事件处理器中。依赖列出 Effect 读取的响应式值,不能通过删依赖来压制规则。
9. Effect cleanup 何时运行?Strict Mode 为什么会重复执行?
依赖变化时,React 先用旧值运行 cleanup,再以新值运行 setup;卸载时运行最后一次 cleanup。开发环境 Strict Mode 会额外做一次 setup/cleanup 检查,验证代码能否正确重入。这是发现资源泄漏的开发检查,不代表生产每次挂载都必定重复执行。
10. Effect 中请求数据怎样避免旧响应覆盖新结果?
请求应允许取消或在 cleanup 中标记旧请求失效,并在提交状态前确认响应仍对应当前参数。AbortController 可中止浏览器侧 fetch,但不保证服务端没收到请求。带缓存、去重和路由加载能力的应用,应评估框架 loader 或服务器状态库,而不是把所有请求散落在 Effect 中。
11. useRef 和 state 有什么区别?
ref 在同一组件实例的 render 间保持可变值,修改 .current 不会触发重新 render;适合 DOM 节点、定时器 ID 或不参与界面输出的命令式信息。会影响屏幕内容的数据应放 state,而不是藏进 ref。
12. Context 适合解决什么问题?
Context 适合跨多层传递主题、当前用户等相对稳定的树级依赖,避免逐层透传。它不是自动适合所有全局状态的工具:value 引用变化会通知消费者,巨大且高频更新的对象可能导致广泛重渲染。先界定状态所有者和订阅范围,再考虑拆分 Context 或外部 store。
13. useMemo、useCallback 是正确性工具吗?
不是。它们缓存计算结果或函数引用,主要用于有证据的性能优化,例如昂贵计算或配合已测量有效的 memo 边界。先用 Profiler 识别瓶颈;不要让缓存成为业务逻辑成立的前提,也不要把每个函数都包起来。
14. 自定义 Hook 会共享状态吗?
自定义 Hook 复用有状态逻辑,不会因为 Hook 函数相同就自动共享同一份 state。两个组件分别调用同一个自定义 Hook,通常各自拥有独立 Hook 状态;共享状态要由共同父组件、Context 或外部 store 提供。
组件、路由与工程
15. React.lazy、Suspense 和错误边界分别做什么?
lazy 延迟加载组件模块,Suspense 在受支持的加载边界尚未就绪时显示 fallback。错误边界负责渲染错误降级 UI;它不会自动捕获事件处理器、任意异步回调或服务端渲染中的错误。加载失败还应有重试或刷新恢复策略。
16. createRoot 和 hydrateRoot 如何选择?
纯客户端挂载通常通过 createRoot(container).render(...)。已有服务端生成且与客户端首屏匹配的 HTML 时,用框架规定的 hydration API(浏览器端通常是 hydrateRoot)接管,不能先清空再 createRoot。优先遵循框架入口,不要在多个位置重复创建 root。
17. React Router 的常见迁移点是什么?
先核对仓库锁文件中的主版本。React Router v5 常见 Switch、useHistory 和 activeClassName;v6 及后续版本通常使用 Routes、useNavigate,NavLink 通过 className 回调读取 isActive。v6+ 的路由匹配采用排序机制,迁移不能只做字符串替换;参数、嵌套路由、loader 和错误边界都要按实际模式验证。详见路由指南和对应主版本官方文档。
18. 前端路由的登录守卫能保护资源吗?
不能。路由守卫只控制用户界面和跳转体验;API 服务必须在每个受保护请求上重新验证身份、角色和资源权限。不要把是否隐藏某个按钮或路由当成授权机制。
19. 服务端组件与客户端组件是什么关系?
Server Components 是 React 能力,但需要支持它的框架和打包/服务端运行时配合。普通 Vite 客户端 SPA 不能仅靠给组件加一个约定标记就获得完整 RSC 能力;按所选框架的边界使用服务端数据访问和客户端交互。
20. React 性能问题应该怎样定位?
先复现并测量:检查网络瀑布、JS 体积、组件 render、长任务和列表规模。再判断瓶颈来自服务端状态请求、过宽订阅、昂贵计算还是 DOM 数量,针对原因采用缓存、代码拆分、虚拟列表或 memo。优化前后都用同一场景比较;不要用 forceUpdate 或任意 memo 掩盖数据流错误。
21. 测试 React 组件时重点是什么?
围绕用户可观察行为测试:交互后显示了什么、加载/错误/空态如何变化、键盘能否完成操作、请求失败是否可恢复。少依赖内部 state 和某个生命周期调用次数;为异步请求、路由跳转和 Strict Mode 重入分别覆盖关键边界。
过时 API 速查
| 旧答案 | 现代核对方式 |
|---|---|
useHistory() / Switch | 常见于 React Router v5;v6 及后续版本通常使用 useNavigate() / Routes |
activeClassName / activeStyle | 核对路由主版本;v6+ NavLink 通过 className / style 回调获得活动状态 |
exact 对每个路由都必不可少 | 这是旧版本常见配置;现代路由有自己的匹配和排序规则,按当前版本验证路径边界 |
| “虚拟 DOM 就是 React 直接操作 DOM 的对象” | React 先计算 UI 描述并协调更新,提交阶段再由对应 renderer 更新宿主环境;避免过度简化为固定数据结构 |
| “Hooks 只能在 class 组件里用” | Hooks 用于函数组件和自定义 Hook;类组件仍受支持,但不能调用 Hooks |
| “useMemo/useCallback 一定能让组件更快” | 它们有依赖与比较成本;只有测量证明有效时才保留 |
面试回答框架
回答一个技术问题时,可以依次说明:目标问题是什么、数据和状态由谁拥有、React 在何时 render/commit、失败或并发情况下的边界是什么、如何验证用户体验与性能。遇到 API 题先声明使用的 React 和路由主版本,再写当前版本的最小例子。
官方资料
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。