这篇不是把网上旧题重新搬一遍,而是按照 2026 年更常见的 React 面试主线重新整理。重点不再是背一堆旧生命周期,也不是记住某个库的 API,而是能讲清楚 React 的渲染模型、状态模型、 Hooks、性能优化、服务端渲染、 Server Components,以及真实项目里如何处理数据、权限、表单和复杂状态。
这 60 道题按照由浅入深排列,适合面试前快速查漏补缺,也适合用来建立自己的 React 知识树。
01|基础层——JSX、组件、Props、State
1. React 是什么?它主要解决了什么问题?
考察点——是否理解 React 的定位,而不是只会说「用于构建用户界面」。
回答抓手——React 是一个声明式 UI 库。开发者描述某个状态下 UI 应该长什么样,React 负责在状态变化后计算并更新界面。它解决的核心问题是组件化、状态驱动 UI、复杂界面更新和复用问题。
2. JSX 是什么?浏览器能直接执行 JSX 吗?
考察点——是否理解 JSX 与 JavaScript 的关系。
回答抓手——JSX 是 JavaScript 的语法扩展,用来描述 UI 结构。浏览器不能直接执行 JSX,需要经过编译工具转换成 JavaScript。它最终表达的是 React 元素,而不是 HTML 字符串。
3. React 元素和 React 组件有什么区别?
考察点——是否理解组件调用和元素描述之间的区别。
回答抓手——React 元素是一个描述 UI 的普通对象,组件是返回 React 元素的函数或类。组件是一种生成 UI 的方式,元素是某一时刻 UI 的描述结果。
4. 函数组件为什么必须保持纯函数特征?
考察点——是否理解渲染过程中的副作用风险。
回答抓手——函数组件在渲染时会被 React 调用,它应该根据 props 和 state 返回 UI,不应该在渲染阶段修改外部变量、发请求或操作 DOM。否则会导致渲染结果不可预测,也会影响并发渲染和重复渲染场景。
5. Props 和 State 有什么区别?
考察点——是否理解数据来源和所有权。
回答抓手——Props 来自父组件,由父组件控制,当前组件不能直接修改。State 是组件自身维护的数据,会触发组件重新渲染。简单说,props 是外部传入,state 是内部拥有。
6. 为什么不能直接修改 State?
考察点——是否理解不可变更新。
回答抓手——React 依赖状态引用变化来判断是否需要更新。如果直接修改对象或数组,引用可能不变,React 可能无法正确识别变化。同时直接修改会破坏状态快照,导致调试和渲染结果不稳定。
7. 什么是受控组件和非受控组件?
考察点——是否理解表单状态由谁控制。
回答抓手——受控组件的表单值由 React state 控制,每次输入都会触发状态更新。非受控组件的值由 DOM 自己维护,React 通过 ref 在需要时读取。复杂表单、校验和提交状态通常更适合受控组件。
8. 条件渲染有哪些常见写法?
考察点——是否熟悉基础渲染模式。
回答抓手——常见写法包括 if 提前返回、三元表达式、逻辑与、枚举映射。简单条件可以用三元表达式,复杂条件最好提前拆成变量或子组件,避免 JSX 里嵌套过深。
9. 列表渲染为什么需要 key?
考察点——是否理解 React 如何识别列表项。
回答抓手——key 用来帮助 React 判断列表中每个元素的身份。它影响节点复用、状态保留和更新效率。没有稳定 key 时,React 可能错误复用 DOM 或组件状态。
10. 为什么不建议用数组下标作为 key?
考察点——是否理解列表变化时的状态错位问题。
回答抓手——如果列表只展示静态数据,数组下标问题不大。但如果列表会新增、删除、排序或过滤,使用下标会让 React 错误地把旧状态复用到新的列表项上,导致输入框错位、动画异常或局部状态混乱。
02|核心机制层——渲染、状态快照、批处理
11. React 中一次状态更新后发生了什么?
考察点——是否理解完整渲染链路。
回答抓手——状态更新后,React 会安排一次重新渲染。组件函数重新执行,生成新的 UI 描述。React 比较新旧结果,然后在提交阶段把必要变化更新到 DOM。完整过程可以理解为触发更新、 Render、 Commit、浏览器绘制。
12. Render 阶段和 Commit 阶段有什么区别?
考察点——是否理解 React 更新过程。
回答抓手——Render 阶段负责计算新的 UI 结果,可以被中断、重试或丢弃。Commit 阶段负责把变化真正应用到 DOM,并执行 layout effect 等提交后的逻辑。Render 是计算,Commit 是落地。
13. setState 之后为什么不能马上拿到最新值?
考察点——是否理解状态快照。
回答抓手——在一次渲染中,state 是固定的快照。调用 setState 不是立即修改当前变量,而是告诉 React 下一次渲染要使用新的状态。所以当前事件处理函数里读到的仍然是这一轮渲染时的旧值。
14. 什么是状态快照?
考察点——是否理解函数组件闭包和状态关系。
回答抓手——组件每次渲染都会拿到当次渲染的 props 和 state,这些值形成一个快照。事件处理函数也会闭包住这一轮快照。因此同一个函数里多次读取 state,读到的是同一轮渲染中的值。
15. React 的批处理是什么?
考察点——是否理解多次状态更新为何合并。
回答抓手——批处理是指 React 会把同一个事件或同一批任务里的多个状态更新合并成一次渲染,减少不必要的 DOM 更新,提高性能。React 18 以后,异步场景中的自动批处理能力也更完善。
16. 多次执行 setCount(count + 1),为什么可能只加 1?
考察点——是否理解状态快照和函数式更新。
回答抓手——因为每次 setCount(count + 1) 使用的都是当前渲染快照里的同一个 count 值。如果需要基于上一次更新结果继续计算,应该使用函数式更新,例如 setCount(prev => prev + 1)。
17. 什么操作会触发组件重新渲染?
考察点——是否理解渲染触发来源。
回答抓手——组件自身 state 更新、父组件重新渲染、Context 值变化、外部 store 订阅变化,都会让相关组件重新渲染。重新渲染不等于 DOM 一定变化,它只是 React 重新计算 UI。
18. 父组件重新渲染,子组件一定会重新渲染吗?
考察点——是否理解默认渲染行为和 memo 优化。
回答抓手——默认情况下,父组件重新渲染时,子组件也会重新执行渲染。即使子组件 props 没变,也会执行。可以通过 React.memo、拆分状态、稳定 props 等方式减少不必要渲染。
19. React 如何决定保留还是重置组件状态?
考察点——是否理解组件身份。
回答抓手——React 根据组件在 UI 树中的位置、组件类型和 key 判断组件身份。如果位置和类型不变,状态通常会保留。如果 key 变化或组件类型变化,React 会认为这是新组件,从而重置状态。
20. 为什么修改 key 可以重置组件状态?
考察点——是否理解 key 不只是列表字段。
回答抓手——key 会参与组件身份判断。当 key 改变时,React 会卸载旧组件并创建新组件,所以旧 state 会被丢弃。这在重置表单、切换用户详情、重新初始化组件时很常用。
03|Hooks 层——useState、useEffect、useRef、自定义 Hook
21. Hooks 解决了什么问题?
考察点——是否理解 Hooks 的出现背景。
回答抓手——Hooks 让函数组件也能使用状态、副作用、引用和上下文。它解决了类组件逻辑复用困难、生命周期逻辑分散、组件写法复杂的问题,让有状态逻辑可以通过自定义 Hook 复用。
22. useState 的初始值什么时候生效?
考察点——是否理解初始值只在首次渲染使用。
回答抓手——useState 的初始值只在组件首次渲染时使用。后续重新渲染不会重新初始化 state。如果初始值计算成本较高,可以传入函数作为惰性初始化。
23. useEffect 是做什么的?
考察点——是否理解 Effect 的正确定位。
回答抓手——useEffect 用来让组件与外部系统同步,例如请求数据、订阅事件、设置定时器、操作浏览器 API。它不是用来组织普通业务计算的工具。如果一个值可以在渲染期间由 props 或 state 计算出来,就不应该再用 Effect 写入另一个 state。
24. useEffect 的依赖数组有什么作用?
考察点——是否理解依赖和重新执行。
回答抓手——依赖数组告诉 React,这个 Effect 依赖哪些响应式值。当依赖变化时,Effect 会重新执行。如果依赖数组为空,Effect 通常只在挂载后执行一次。如果不写依赖数组,每次渲染后都会执行。
25. useEffect 为什么需要清理函数?
考察点——是否理解资源释放和副作用撤销。
回答抓手——清理函数用于取消订阅、清除定时器、中止请求或撤销上一次 Effect 的副作用。否则组件卸载后仍可能触发回调,造成内存泄漏、重复监听或状态更新异常。
26. useEffect 中为什么容易出现闭包旧值?
考察点——是否理解 Effect 闭包和依赖关系。
回答抓手——Effect 函数会闭包住当前渲染中的变量。如果依赖数组缺少某个变量,Effect 后续执行时可能仍然读取旧值。解决方式是补全依赖、使用函数式更新、使用 ref 保存最新值,或者重新设计数据流。
27. useEffect 和 useLayoutEffect 有什么区别?
考察点——是否理解执行时机。
回答抓手——useEffect 通常在浏览器绘制后执行,不阻塞页面展示。useLayoutEffect 在 DOM 更新后、浏览器绘制前同步执行,适合测量布局或同步修改布局。多数业务场景应该优先使用 useEffect,避免 useLayoutEffect 阻塞绘制。
28. useRef 和 useState 有什么区别?
考察点——是否理解可变引用和渲染状态。
回答抓手——useState 的变化会触发重新渲染,适合存放需要展示在 UI 中的数据。useRef 的 current 可以保存可变值,但修改它不会触发渲染,适合保存 DOM、定时器 ID、上一次值或不参与 UI 展示的数据。
29. 自定义 Hook 解决了什么问题?
考察点——是否理解逻辑复用,而不是状态共享。
回答抓手——自定义 Hook 用来复用有状态逻辑,例如请求逻辑、窗口尺寸监听、表单逻辑、防抖逻辑。每次调用自定义 Hook 都有自己独立的状态,它不是默认共享状态的工具。
30. Hooks 为什么不能写在条件语句或循环里?
考察点——是否理解 Hooks 调用顺序。
回答抓手——React 依赖 Hooks 的调用顺序来关联每个 Hook 对应的状态。如果把 Hook 放在条件语句或循环中,不同渲染之间调用顺序可能变化,React 就无法正确匹配状态。
04|状态管理层——Context、Reducer、服务端状态、URL 状态
31. Context 适合解决什么问题?
考察点——是否理解跨层级传值。
回答抓手——Context 适合解决主题、语言、当前用户、权限信息等跨层级共享问题。它可以避免层层传递 props,但不适合无脑存放所有状态,因为 Context 值变化会影响所有消费它的组件。
32. Context 有什么性能问题?
考察点——是否理解 Provider value 变化的影响。
回答抓手——当 Provider 的 value 引用变化时,所有消费该 Context 的组件都可能重新渲染。优化方式包括拆分 Context、稳定 value、缩小 Provider 范围,或者对高频变化状态使用外部状态管理库。
33. useReducer 适合什么场景?
考察点——是否理解复杂状态转移。
回答抓手——useReducer 适合状态结构复杂、更新逻辑集中、多个 action 影响同一份状态的场景。它让状态变化更可预测,适合表单、多步骤流程、复杂筛选条件等。
34. useState 和 useReducer 怎么选择?
考察点——是否理解状态复杂度。
回答抓手——简单独立状态用 useState 更直接。复杂状态、多个字段联动、更新逻辑需要集中管理时,用 useReducer 更清晰。选择依据不是哪个更高级,而是状态转移是否复杂。
35. 什么时候需要 Redux 或 Zustand 这类状态库?
考察点——是否理解全局状态的边界。
回答抓手——当多个远距离组件共享状态、状态更新频繁、业务流程复杂、需要调试和中间件时,可以考虑状态库。小项目或局部状态不要一上来就引入全局状态库。
36. 为什么不建议把所有接口数据都放进 Redux?
考察点——是否区分客户端状态和服务端状态。
回答抓手——接口数据本质上是服务端状态,需要处理缓存、过期、重新获取、错误、加载、分页和并发。Redux 更适合管理客户端状态。如果是服务端状态,通常更适合使用 TanStack Query、SWR 或框架自带的数据获取能力。
37. 什么是服务端状态?
考察点——是否理解数据所有权。
回答抓手——服务端状态是由服务端拥有的数据,前端只是缓存和展示。例如用户列表、订单列表、订阅状态、文章详情。它的问题不是简单 setState,而是缓存、同步、重新验证、请求取消和错误处理。
38. 什么状态适合放到 URL 中?
考察点——是否理解可分享和可恢复状态。
回答抓手——搜索关键词、分页、筛选条件、排序方式、Tab、详情 ID 等适合放在 URL 中。这样页面可以刷新恢复,也可以分享链接。只存在组件 state 中的状态,刷新后就会丢失。
39. 如何处理接口重复请求?
考察点——是否理解请求去重和缓存。
回答抓手——可以通过请求状态缓存、AbortController、中间层封装、TanStack Query、SWR 或框架缓存能力处理。关键是避免多个组件重复发同一个请求,也要避免旧请求晚返回覆盖新状态。
40. 如何处理请求竞态问题?
考察点——是否理解异步返回顺序不可控。
回答抓手——请求竞态指后发请求先返回或旧请求晚返回覆盖新结果。常见处理方式包括记录请求 ID、使用 AbortController 取消旧请求、只接受最后一次请求结果,或者使用成熟的数据请求库。
05|性能优化层——memo、useMemo、useCallback、代码分割
41. React 项目如何做性能优化?
考察点——是否有系统思路。
回答抓手——不要一上来就说 useMemo。正确顺序是先用 Profiler 或 Performance 定位问题,再减少不必要状态,缩小状态作用域,拆分组件,避免无意义重新渲染,最后再考虑 memo、useMemo、useCallback、懒加载和虚拟列表。
42. React.memo 是做什么的?
考察点——是否理解 props 浅比较。
回答抓手——React.memo 用于缓存组件渲染结果。当父组件重新渲染时,如果子组件 props 浅比较没有变化,React 可以跳过子组件重新渲染。它适合纯展示组件或渲染成本较高的组件。
43. 为什么用了 React.memo,组件还是重新渲染?
考察点——是否理解引用变化。
回答抓手——如果传入的 props 每次都是新引用,例如新对象、新数组、新函数,浅比较会认为 props 变化,memo 就无法生效。另外 Context 变化、组件自身 state 变化也会让组件重新渲染。
44. useMemo 是做什么的?
考察点——是否理解缓存计算结果。
回答抓手——useMemo 用来缓存计算结果,只有依赖变化时才重新计算。它适合成本较高的计算,或者用于稳定对象、数组引用。但普通轻量计算没必要使用 useMemo。
45. useCallback 是做什么的?
考察点——是否理解缓存函数引用。
回答抓手——useCallback 用来缓存函数引用,只有依赖变化时才返回新函数。它常用于把回调传给 memo 子组件,避免子组件因为函数引用变化而重新渲染。它不是让函数执行更快。
46. useMemo 和 useCallback 有什么区别?
考察点——是否能准确区分。
回答抓手——useMemo 缓存的是计算结果,useCallback 缓存的是函数引用。可以理解为 useCallback(fn, deps) 大致相当于 useMemo(() => fn, deps)。
47. 什么时候不应该使用 useMemo 和 useCallback?
考察点——是否知道优化也有成本。
回答抓手——如果计算很轻、组件不频繁渲染、子组件没有 memo,或者依赖经常变化,使用 useMemo 和 useCallback 可能收益很低,反而增加代码复杂度。性能优化应该基于定位,而不是默认套用。
48. 什么是代码分割?
考察点——是否理解加载性能。
回答抓手——代码分割是把应用代码拆成多个 chunk,按需加载,减少首屏 JavaScript 体积。React 中可以使用 React.lazy 和 Suspense,框架层也可以做路由级分包。
49. Suspense 解决什么问题?
考察点——是否理解异步边界。
回答抓手——Suspense 用来为异步加载的内容提供 fallback,例如懒加载组件、某些数据读取和流式渲染场景。它让异步 UI 可以通过边界组织,而不是到处写 loading 判断。
50. 虚拟列表适合什么场景?
考察点——是否理解大列表性能瓶颈。
回答抓手——当列表数据量很大,例如几千条、几万条时,一次渲染全部 DOM 会导致页面卡顿。虚拟列表只渲染视口附近的项目,减少 DOM 数量,提高滚动性能。
06|工程实践层——表单、错误、测试、安全
51. 如何设计一个通用 Modal 组件?
考察点——是否有组件设计能力。
回答抓手——需要考虑受控开关、标题、内容、底部按钮、关闭回调、遮罩点击、键盘 Escape、焦点管理、滚动锁定、Portal 渲染和可访问性。核心是让状态由外部控制,内部只处理交互细节。
52. 如何设计一个复杂表单?
考察点——是否理解表单状态管理。
回答抓手——需要区分字段值、校验状态、提交状态、服务端错误、脏数据状态和禁用状态。简单表单可以受控组件自己写,复杂表单可以使用表单库。重点是防止重复提交、错误提示清晰、提交失败可恢复。
53. React 中如何处理错误边界?
考察点——是否理解 Error Boundary 的范围。
回答抓手——Error Boundary 可以捕获渲染阶段、生命周期和子组件中的错误,并展示降级 UI。但它不能捕获事件处理函数里的错误,也不能自动捕获普通异步请求错误。事件和请求错误需要业务代码自己处理。
54. 如何处理 Loading、Error、Empty、Success 四种状态?
考察点——是否有真实业务经验。
回答抓手——接口请求不应该只考虑成功。组件需要明确 loading、error、empty、success 四种状态。复杂页面还要区分首次加载、局部刷新、分页加载、提交中和重新验证状态。
55. React 项目如何做权限控制?
考察点——是否理解前后端权限边界。
回答抓手——前端可以做路由拦截、按钮隐藏、菜单过滤和页面降级,但真正权限必须在服务端校验。前端权限是体验层,服务端权限才是安全边界。
56. React 项目如何防止 XSS?
考察点——是否理解前端安全。
回答抓手——避免渲染不可信 HTML,慎用 dangerouslySetInnerHTML,对用户输入和富文本内容进行清理,避免把不可信内容拼进 HTML。React 默认会转义普通文本,但手动插入 HTML 时仍然有风险。
57. React 项目应该怎么测试?
考察点——是否理解测试分层。
回答抓手——单元测试用于测试纯函数和工具方法,组件测试用于测试用户交互和渲染结果,端到端测试用于测试完整业务流程。React 组件测试更推荐关注用户行为,而不是测试内部实现细节。
07|Next.js 与现代 React 层——SSR、水合、RSC、App Router
58. CSR、SSR、SSG 有什么区别?
考察点——是否理解渲染方式。
回答抓手——CSR 是浏览器下载 JavaScript 后在客户端渲染页面。SSR 是服务端为每次请求生成 HTML。SSG 是构建时提前生成静态 HTML。选择方式要看页面是否需要 SEO、是否数据实时、是否可缓存、首屏性能要求如何。
59. 什么是 hydration?为什么会出现水合不一致?
考察点——是否理解服务端 HTML 和客户端交互接管。
回答抓手——hydration 是客户端 JavaScript 接管服务端生成 HTML 的过程,让静态 HTML 变得可交互。水合不一致通常来自服务端和客户端首次渲染结果不同,例如时间、随机数、浏览器 API、用户状态或条件渲染不一致。
60. Server Component 和 Client Component 有什么区别?
考察点——是否理解现代 React 和 Next.js 的核心边界。
回答抓手——Server Component 在服务端渲染,可以直接访问服务端资源,不进入客户端 JavaScript 包,但不能使用 state、Effect、事件处理和浏览器 API。Client Component 可以使用交互能力,需要通过 "use client" 声明边界。实际项目中应该尽量缩小客户端组件范围,把交互留给 Client Component,把数据读取和静态结构尽量放在 Server Component。
最后总结
如果只准备 React 面试,不要把精力平均分给所有知识点。真正高频的是状态模型、渲染机制、 useEffect、组件设计、状态管理、性能优化和项目经验。对于 Next.js 项目,还必须补上 SSR、水合、 Server Component、 Client Component 和 App Router 的边界。
如果面试时间很紧,可以先把前 30 题讲清楚,再准备 41 到 60 题。这样至少能覆盖大多数 React 面试里的核心问题。旧的类组件生命周期、 HOC、 Render Props、旧 Redux、旧 React Router,可以知道概念,但不必作为主线背诵。