React 高频面试题 50 道(整理版)
一、核心概念(1-10)
1. React 是什么?它的核心理念是什么?
答:React 是一个用于构建用户界面的声明式、组件化的开源 JS 库。核心理念是声明式(Declarative) 和组件化(Component-Based),通过组件抽象实现跨平台(Web / React Native)。
2. 虚拟 DOM 的工作原理是什么?真实 DOM 和虚拟 DOM 的区别?
答:虚拟 DOM 是一个轻量的 JS 对象树,组件更新时先 diff 新旧虚拟 DOM 树,计算出最小变更集 (patch),再批量更新真实 DOM。真实 DOM 是浏览器提供的 API 对象,操作成本高;虚拟 DOM 是 普通的 JS 对象,仅用于描述 UI 结构,不限于浏览器环境。
3. 什么是 JSX?浏览器为什么不直接理解 JSX?
答:JSX 是 JavaScript XML 的缩写,是 React 推荐的语法糖,看起来像 HTML 但实际是 JS。 浏览器只认识标准 JS,JSX 需要通过 Babel 转译成 React.createElement() 调用才能执行。
4. React 元素和 React 组件有什么区别?
答:React 元素是一个不可变的普通 JS 对象({ type, props, children }),描述你想在屏幕上 看到什么,是最小的结构单元。组件是函数或类,接受 props 并返回 React 元素,是 UI 的 抽象单元。
5. 函数组件和类组件有什么区别?什么时候用哪个?
答:函数组件是纯函数,更轻量,逻辑复用靠 Hooks;类组件有 this 绑定和生命周期方法。 现在新项目全部用函数组件 + Hooks,类组件已是 legacy。
6. 什么是受控组件和非受控组件?
答:受控组件:表单值由 React state 管理,通过 onChange 事件更新 state,React 是"唯一 数据源"。非受控组件:DOM 自己维护表单状态,通过 ref 获取值,类似传统 HTML 表单。
7. 什么是 React 中的 key?为什么渲染列表时不能用 index 作为 key?
答:key 帮助 React 识别列表中哪些项被增/删/改。用 index 做 key 的问题:数组项顺序 一旦变化(头部插入、排序、删除中间项),React 会错误复用旧的组件实例和 state,导致 渲染 bug(如输入框内容错位)或性能变差。
8. React 中 state 和 props 的区别是什么?
答:state 是组件自身的、可变的内部状态,通过 setState 更新。props 是父组件传递给 子组件的只读数据,子组件不能修改。
9. React 中的 setState 是同步还是异步的?
答:取决于上下文。在 React 合成事件和生命周期中是异步的(批处理);在 setTimeout、 原生 DOM 事件、Promise 回调中,React 17 是同步的。React 18 引入 Automatic Batching 后,几乎所有场景都是异步批处理。
10. React 的事件机制是什么?和原生 DOM 事件有什么区别?
答:React 实现了合成事件(SyntheticEvent),做了一层浏览器兼容封装。React 17 后将 事件委托挂载在 root 节点(而非 document)。区别:命名方式不同(onClick vs onclick)、 不能 return false 阻止默认行为(需调用 preventDefault)、合成事件对象会被回收(异步 访问需调用 e.persist())。
二、生命周期与渲染机制(11-20)
11. 类组件的生命周期方法有哪些?每个阶段对应什么方法?
答:三大阶段:
- 挂载(Mounting):constructor -> getDerivedStateFromProps -> render -> componentDidMount
- 更新(Updating):getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate
- 卸载(Unmounting):componentWillUnmount 注意:componentWillReceiveProps 等已标记为 UNSAFE_。
12. React 的渲染流程是什么?
答:触发更新(Trigger)-> Render Phase(调用组件函数,生成新的虚拟 DOM 树)-> Reconciliation / Diff(对比新旧虚拟 DOM)-> Commit(将最小变更集应用到真实 DOM)-> Browser Paint(浏览器重绘)。
13. 什么是 React 的调和(Reconciliation)过程?
答:调和是 React 的 diff 算法,通过对比新旧两棵虚拟 DOM 树,决定哪些节点需要增/删/改。 核心策略:(1) 只对比同一层级节点;(2) 不同类型组件直接替换整棵子树;(3) 通过 key 优化列表 diff。
14. 什么是 Fiber?Fiber 架构解决了什么问题?
答:Fiber 是 React 16 引入的新协调引擎。Fiber 节点是一个 JS 对象,通过 child / sibling /
return 指针构成链表树。旧架构(Stack Reconciler)一旦递归 diff 就无法中断,长时间占用主线程导致掉帧。Fiber 把更新拆成可中断的小单元,配合优先级调度,让高优先级更新 (如用户输入)能插队执行。
15. React 中的批处理(Batching)机制是什么?React 18 有什么变化?
答:批处理指 React 将多次 setState 合并成一次更新,只触发一次渲染。React 17 及以前 只在合成事件和生命周期中批处理;React 18 引入 Automatic Batching,setTimeout、
Promise 回调、原生事件等所有场景默认都做批处理。16. React.StrictMode 的作用是什么?为什么开发环境会渲染两次?
答:StrictMode 在开发环境做以下检查:检测不安全的生命周期、废弃的 ref API、意外的 副作用、废弃的 findDOMNode。故意双重调用 render / setState / effects 是为了暴露 副作用中的 bug,让开发者提前发现副作用不纯的问题。
17. 什么是"副作用"(Side Effect)?在 React 中如何处理?
答:副作用指函数执行过程中与外部环境交互的操作,如网络请求、定时器、DOM 操作、订阅等。 React 中用 useEffect 处理副作用,并通过 cleanup 函数在卸载或重跑 effect 前清除资源, 防止内存泄漏。
18. React 组件什么时候会重新渲染?如何避免不必要的重渲染?
答:当 state 或 props 改变时组件会重新渲染(包括父组件重渲染导致子组件重渲染)。 避免手段:React.memo(浅比较 props)、PureComponent、shouldComponentUpdate、 useMemo / useCallback 保持引用稳定。
19. 什么是 React 的 Portal?用在什么场景?
答:Portal(ReactDOM.createPortal(children, domNode))把子组件渲染到父组件 DOM 树 之外的任意 DOM 节点上。常见场景:Modal(弹窗)、Tooltip、Dropdown——需要避免 overflow: hidden / z-index 上下文裁剪的浮层 UI。
20. Suspense 的作用是什么?
答:Suspense 让组件在等待异步操作(代码加载、数据请求)完成时展示 fallback 备选 UI, 避免白屏或抛错。用法:<Suspense fallback={<Loading />}><LazyComponent /></Suspense>。 常配合 React.lazy 做组件的按需加载。
三、Hooks(21-35)
21. React Hooks 的诞生背景是什么?解决了什么问题?
答:Hooks 解决了 class 组件的三个核心痛点:(1) 逻辑复用困难(HOC/render props 嵌套 导致 Wrapper Hell);(2) 生命周期方法按时间拆分而非按关注点拆分,大组件难以维护; (3) class 的 this 绑定和理解成本高。Hooks 用函数组件实现逻辑复用,组合式复用简单, 无 this 担忧,无生命周期心理负担。
22. 为什么 Hooks 不能在条件语句或循环中使用?
答:Hooks 底层依赖单向链表按调用顺序存储状态。每次渲染 React 按调用顺序将 hook 串成链表,条件或循环会改变 hook 的调用次数或顺序,导致索引错位,state/effect 对应 到错误的 hook 上。
23. useState 的原理是什么?setState 传入函数和传入对象的区别?
答:useState 利用闭包维护一个在多次渲染间持久的状态值。传入函数(setCount(prev => prev + 1))总能获取最新 state,在批处理场景中能正确累加;传入对象或值(setCount(
count + 1))在批处理时只会最后一次生效,因为每次拿到的 count 是同一快照值。24. useEffect 的依赖数组是如何工作的?空数组和省略依赖有什么区别?
答:依赖数组中的值发生变化时 useEffect 重新执行。空数组 [] 只在组件初次挂载时执行 一次(相当于 componentDidMount);省略依赖数组时每次渲染都执行(相当于 componentDidUpdate + componentDidMount)。
25. useEffect 的清理函数(cleanup)在什么时候执行?
答:在组件卸载时执行;在依赖项变化导致 effect 重跑前,先清理上一次 effect 产生的 副作用(如清除定时器、取消订阅)。注意:清理的是副作用本身,不是 state。
26. useLayoutEffect 和 useEffect 的区别是什么?
答:执行时机不同。React 渲染流程:render(生成虚拟 DOM)-> DOM 提交(commit)->
useLayoutEffect(同步执行)-> 浏览器 Paint -> useEffect(异步执行)。所以
useLayoutEffect 在 DOM 变更后、浏览器绘制前同步执行,适合读取布局、测量 DOM 尺寸的场景;useEffect 在绘制后异步执行,适合不阻塞视觉更新的副作用。
27. useRef 的作用是什么?它和普通变量、state 的区别?
答:useRef 返回一个 .current 属性,在组件整个生命周期内引用保持不变。它和 state 的 关键区别:修改 .current 不会触发重新渲染。常用于获取真实 DOM 节点(ref 绑定)、 保存跨渲染周期的可变值(如计时器 ID)。普通变量每次渲染会重置,useRef 不会。
28. forwardRef 是什么?useImperativeHandle 怎么用?
答:forwardRef 是 React 提供的高阶 API,让父组件能获取子组件的 DOM 节点或实例。 useImperativeHandle 配合 forwardRef 使用,限制子组件暴露给父组件的接口,而不是 直接把整个 DOM 节点暴露出去,更安全可控。
29. useContext 的原理和使用场景?
答:useContext 让函数组件直接读取 React.createContext 创建的上下文值,避免逐层 prop drilling。当 Context 的 value 变化时,所有消费该 Context 的组件自动重渲染。 适合主题、语言、登录态等低频变化但跨组件树的状态。
30. useReducer 和 useState 有什么区别?什么时候用 useReducer?
答:useState 适合独立、简单的状态;useReducer 适合多个子值耦合、或下一个状态依赖 前一个状态的复杂逻辑。useReducer 的 reducer 是纯函数,可预测、可单独测试。 简单数据用 useState,复杂数据用 useReducer。
31. useMemo 和 useCallback 的区别是什么?什么场景需要它们?
答:useMemo 缓存计算结果(值),useCallback 缓存函数引用。useCallback(fn, deps) 等价于 useMemo(() => fn, deps)。主要用在配合 React.memo 时,保持传给子组件的 props (回调函数或计算值)引用稳定,避免子组件不必要重渲染。
32. useMemo 和 React.memo 的区别?
答:useMemo 是 Hook,缓存某个值或计算结果,减少不必要的计算开销。React.memo 是 HOC,包裹整个组件,对 props 做浅比较,减少不必要的组件渲染。两者优化层面不同: 一个在值层面,一个在组件层面,可以配合使用。
33. 什么是自定义 Hook?什么场景应该抽象成自定义 Hook?
答:自定义 Hook 是一个以 use 开头的函数,内部可以调用其他 Hooks,用于抽取带状态的 公共逻辑(stateful logic)。场景:当多个组件共享同一套带 state / effect 的逻辑时 (如表单输入管理、鼠标位置追踪、窗口大小监听、数据请求),应抽出自定义 Hook。普通 工具函数不涉及 React 状态,不需要做成自定义 Hook。
34. React 18 引入了哪些新 Hook?(useId、useTransition、useDeferredValue)
答:useTransition:标记低优先级更新,让 UI 在高开销渲染期间保持响应。useDeferredValue: 延迟某个值的更新,效果类似 debounce 但更智能(React 自动决定何时降级旧值)。 useId:生成唯一且稳定的 ID,解决 SSR 场景下客户端与服务端 ID 不匹配的问题。
35. useSyncExternalStore 的作用是什么?
答:让函数组件安全地订阅 React 外部的数据源(如浏览器 API、第三方状态库),同时与 React 18 的并发渲染兼容。主要使用场景是状态管理库的作者(Zustand、Redux 内部会用到), 普通业务开发中不常用。
四、状态管理与数据流(36-42)
36. 什么是"状态提升"(Lifting State Up)?
答:把多个子组件共享的可变状态抽离到它们最近的公共父组件中,父组件通过 props 将 状态和更新函数传递给子组件。这是 React 最基础的数据流模式,用于实现兄弟组件间通信。
37. React Context 的工作原理是什么?Context 会导致什么问题?
答:原理:React.createContext 创建 Provider + Consumer 对。Provider 的 value 变化时, React 从该 Provider 的 Fiber 节点向下遍历,标记消费该 Context 的子 Fiber 为需更新。 问题:(1) 无差别重渲染——所有消费组件的 value 任意字段变化都会重渲染;(2) Provider 嵌套 过深形成"嵌套地狱";(3) 不适合高频变化的状态(如表单输入),因为 Context 没有选择性 更新机制。
38. Context 和 Redux 的区别?什么时候用 Context,什么时候用 Redux/Zustand?
答:Context 是 React 内置的依赖注入机制,解决 prop drilling 问题。Redux 是状态管理 工具库,提供可预测的 state 容器、中间件生态、DevTools。Context + useReducer 可替代 简单场景的 Redux。低频变化跨层级传值(主题/语言/登录态)用 Context;大型项目需要 统一管理状态、中间件、时间旅行调试时用 Redux/Zustand。
39. Redux 的核心概念(store、reducer、action、dispatch)?
答:Store:单一数据源,存放整个应用的状态树。Action:带有 type 字段的普通 JS 对象, 描述"发生了什么"。Reducer:纯函数 (state, action) => newState,根据 action 返回 新状态。Dispatch:触发 action 的唯一方法,将 action 发给 reducer。
40. Redux 中间件(Middleware)的原理?redux-thunk 和 redux-saga 的区别?
答:中间件是一个柯里化函数,在 dispatch 被调用后、到达 reducer 之前拦截 action, 可以执行副作用(日志、异步请求)。多个中间件通过 applyMiddleware 组合成管道。 redux-thunk:允许 dispatch 接收函数(thunk),简单直接,适合少量异步逻辑。 redux-saga:基于 Generator,监听 action 的模式更强大,适合复杂异步流程(竞态处理、 重试、取消),但学习曲线陡峭。
41. Zustand 和 Redux 的区别?Zustand 为什么更轻量?
答:Zustand 基于发布-订阅,不需要 Provider 包裹,API 简洁,体积 ~1KB。内部使用 useSyncExternalStore 接入 React,天然兼容并发模式。支持细粒度 selector 订阅—— 只依赖选中的字段,其他字段变化不触发重渲染。Redux 模板代码多(Provider + reducer + action + dispatch + 中间件),但生态更成熟。
42. React Query(TanStack Query)解决了什么问题?
答:它把服务端状态(server state)与客户端状态(client state)区分开。自动管理缓存、 后台刷新、请求重试、分页、乐观更新。开发者不再需要用 useEffect + useState + try/catch 手动封装请求流水线。Redux/Zustand 管客户端状态,React Query 管服务端状态,两者可以共存。
五、性能优化(43-47)
43. React 性能优化的常见手段有哪些?
答:三大类: (1) 减少不必要的渲染——React.memo / PureComponent / shouldComponentUpdate / useMemo / useCallback 控制组件重渲染。 (2) 延迟加载——React.lazy + Suspense 做组件级 code splitting,路由级别的动态 import。 (3) 减少计算量——虚拟列表(react-window)、 useMemo 缓存计算结果、debounce/throttle 减少高频触发。
44. React.memo 的原理是什么?使用时需要注意什么?
答:React.memo 是一个高阶组件,对传入的 props 做浅比较(shallow compare),如果 props 引用没有变化则跳过渲染,直接复用上次的渲染结果。注意:如果传递了内联对象或内联函数, 每次渲染引用都会变,memo 会失效。需要配合 useMemo / useCallback 保持引用稳定。
45. useMemo / useCallback 为什么不一定能带来性能提升?
答:它们自身有开销——维护依赖数组、做比较运算、保留引用。在简单组件中盲目使用反而 是负优化。React 官方的建议:先写没有优化的版本,测量后有性能问题再考虑加,不要 过早优化。
46. Code Splitting 和 Lazy Loading 在 React 中如何实现?
答:核心 API 是 React.lazy + 动态 import() + Suspense。
const MyPage = React.lazy(() => import('./MyPage'));
<Suspense fallback={<Loading />}><MyPage /></Suspense>也可以结合 react-router 做路由级别的 code splitting,以及 webpack 的 splitChunks 做第三方库分离。
47. 为什么要避免在 useEffect 中频繁 setState?
答:频繁 setState 会导致反复触发 render -> commit -> 重新运行 effect,进入循环或 造成额外的重渲染。如果 effect 依赖项变化,还可能触发无限循环。正确的做法是先判断 是否真的需要更新,或将多次更新合并为一次。
六、工程化与进阶(48-50)
48. Next.js(App Router)和 React 的关系?SSR、SSG、ISR 的区别?
答:Next.js 是 React 的全栈框架,提供文件路由、SSR / SSG / ISR 三种渲染策略。 SSR(Server-Side Rendering):每次请求在服务端生成 HTML,适合动态内容(用户个人页)。 SSG(Static Site Generation):构建时生成静态 HTML,适合博客、文档等不变页面。 ISR(Incremental Static Regeneration):SSG 的升级,部署后按间隔或按需重新生成 静态页面,兼具静态的速度和动态的时效性。
49. React 中的错误处理方案有哪些?Error Boundary 的限制是什么?
答:Error Boundary 是类组件机制,通过 getDerivedStateFromError(展示 fallback UI) 和 componentDidCatch(错误日志上报)捕获子树渲染错误。限制:不能捕获(1) 事件处理 中的错误(需 try/catch)、(2) 异步代码(setTimeout / Promise)、(3) SSR 中的错误、 (4) Error Boundary 自身的错误。可用 react-error-boundary 第三方库或多个 Error Boundary 分层级兜底。
50. React 18 的并发特性(Concurrent Features)是什么?如何开启?
答:并发特性是 React 可中断渲染机制——渲染过程可被更高优先级的更新打断,先处理紧急 任务,再恢复低优先级渲染,保持 UI 响应。通过三个入口开启:(1) createRoot 替代 ReactDOM.render(前提);(2) useTransition 标记低优先级过渡更新;(3) useDeferredValue
延迟特定值的更新。Suspense 在并发模式下也有增强行为(流式 SSR)。
