以下是 100 道 React 中高级面试题及解析,涵盖核心原理、Hooks、状态管理、性能优化等多个维度,适合中高级前端开发者备考参考。
一、React 核心原理(1-10 题)
1. 什么是虚拟 DOM?它为什么能提高性能?
解析:
- 虚拟 DOM(Virtual DOM)是 React 内部维护的、对真实 DOM 的轻量内存映射(JavaScript 对象),描述了 DOM 的结构和属性。
- 性能优化逻辑:
- 真实 DOM 操作昂贵(重绘 / 回流),虚拟 DOM 通过批量对比差异(Diff 算法),只更新变化的部分,减少真实 DOM 操作次数。
- 虚拟 DOM 是 JS 对象,操作成本远低于真实 DOM,且支持跨平台(如 React Native)。
2. React 的 Diff 算法有哪些优化策略?
解析:
React Diff 算法基于三个假设实现高效对比:
- 同层节点只对比同层:不跨层级对比(如父节点变化,直接销毁子树重建)。
- key 的作用:列表节点通过
key
标识唯一性,避免不必要的节点重排(无 key 时可能导致组件状态错乱)。 - 类型不同则销毁重建:若节点类型(如
div
vsspan
)不同,直接销毁旧节点并创建新节点,不深入对比子节点。
3. 什么是 Fiber 架构?它解决了什么问题?
解析:
- Fiber 是 React 16 引入的新协调引擎,本质是可中断、可恢复的工作单元(将渲染任务拆分为小片段)。
- 解决的问题:
传统 Stack reconciler(栈协调)是同步任务,若渲染任务复杂(如长列表),会阻塞主线程(导致 UI 卡顿、事件响应延迟)。
Fiber 通过时间切片(Time Slicing) 和优先级调度(如用户输入 > 动画 > 渲染),允许任务中断、暂停、恢复或放弃,保证主线程不被阻塞。
4. React 的生命周期分为几个阶段?每个阶段有哪些常用生命周期方法?
解析:
分为 3 个阶段:
-
挂载阶段(Mounting):组件初始化并插入 DOM
constructor
:初始化状态、绑定方法static getDerivedStateFromProps
:从 props 派生 state(替代componentWillReceiveProps
)render
:生成虚拟 DOMcomponentDidMount
:DOM 挂载后执行(如请求数据、事件监听)
-
更新阶段(Updating):props/state 变化时
static getDerivedStateFromProps
shouldComponentUpdate
:控制是否重渲染(默认 true,可优化性能)render
getSnapshotBeforeUpdate
:在 DOM 更新前获取快照(如滚动位置)componentDidUpdate
:DOM 更新后执行(如根据 props 变化请求数据)
-
卸载阶段(Unmounting):
componentWillUnmount
:组件卸载前清理(如移除事件监听、取消请求)
5. React 的合成事件(SyntheticEvent)与原生事件有什么区别?
解析:
维度 | 合成事件 | 原生事件 |
---|---|---|
事件委托 | 委托到 document(统一管理) | 需手动绑定到具体 DOM 节点 |
事件对象 | 跨浏览器兼容的 SyntheticEvent 实例 | 浏览器原生 Event 对象 |
阻止冒泡 | e.stopPropagation() | 除了e.stopPropagation() ,还需e.preventDefault() (部分场景) |
执行顺序 | 先于原生事件(因委托机制) | 后于合成事件 |
注意:合成事件中e.nativeEvent
可获取原生事件对象,但不建议混合使用两者(可能导致逻辑混乱)。
6. 什么是受控组件与非受控组件?如何选择?
解析:
-
受控组件:表单元素(如 input、select)的状态由 React 的 state 控制,通过
onChange
同步更新(如value={state} onChange={(e) => setState(e.target.value)}
)。
优点:状态统一管理,可实时验证;缺点:代码略繁琐。 -
非受控组件:表单状态由 DOM 自身维护,通过
ref
获取值(如ref={(el) => this.input = el}
,取值时用this.input.value
)。
优点:代码简洁;缺点:状态分散,难实时验证。
选择原则:
- 需实时验证、联动控制(如表单提交前校验)→ 受控组件;
- 简单场景(如文件上传)、兼容第三方库 → 非受控组件。
7. React 中setState
是同步还是异步的?为什么?
解析:
-
分场景:
- 在 React 合成事件(如
onClick
)、生命周期方法中:异步更新(批量合并,提升性能)。 - 在原生事件(如
addEventListener
)、定时器(setTimeout
)、Promise 回调中:同步更新(脱离 React 控制流)。
- 在 React 合成事件(如
-
原理:React 通过
isBatchingUpdates
标记是否批量更新。合成事件 / 生命周期中,该标记为true
,setState
会先入队列,结束后批量执行;其他场景标记为false
,立即执行。 -
如何获取更新后的值:使用
setState
的回调函数(setState(updater, callback)
)或useEffect
(Hooks 中)。
8. 什么是 Context?它的使用场景和局限性是什么?
解析:
- Context 是 React 提供的跨组件数据传递方案(避免 “props drilling”:多层级传递 props)。
- 使用场景:全局状态(如主题、用户信息)、跨少数层级的组件通信。
- 使用方式:
jsx
const ThemeContext = React.createContext('light'); // 创建Context // 父组件提供值 <ThemeContext.Provider value="dark"> <Child /> </ThemeContext.Provider> // 子组件消费值 const Child = () => { const theme = useContext(ThemeContext); // Hooks方式 return <div>{theme}</div>; };
- 局限性:
- 频繁更新会导致所有消费组件重渲染(性能问题),不适合频繁变化的状态;
- 不能替代状态管理库(如 Redux),复杂状态建议用专门的库。
9. React 中key
的作用是什么?为什么不能用索引作为key
?
解析:
-
key
是 React 识别列表节点唯一性的标识,用于优化 Diff 算法:若key
不变,React 认为节点未变,可复用;若key
变化,节点会被销毁重建。 -
用索引作为
key
的问题:
当列表增删、排序时,索引会变化(如删除第一项,后续项索引都减 1),导致 React 误判节点变化,引发:- 不必要的节点销毁重建(性能浪费);
- 组件状态丢失(如输入框内容错乱)。
-
推荐:用节点唯一 ID(如后端返回的
id
)作为key
。
10. React 与 Vue 的核心区别是什么?
解析:
维度 | React | Vue |
---|---|---|
核心思想 | 函数式编程(组件即函数) | 响应式编程(数据驱动) |
模板 | JSX(JavaScript 中写 HTML) | 独立模板(HTML 中写指令) |
状态管理 | 需第三方库(Redux 等) | 内置 Vuex/Pinia |
虚拟 DOM | 全量对比(Fiber 优化) | 基于依赖追踪的精确更新 |
学习曲线 | 较陡(需理解 JSX、Hooks 等) | 较平缓(HTML/CSS/JS 友好) |
二、Hooks 深入(11-25 题)
11. 什么是 Hooks?它解决了什么问题?
解析:
- Hooks 是 React 16.8 引入的特性,允许在函数组件中使用状态和生命周期功能(无需编写类组件)。
- 解决的问题:
- 类组件的生命周期逻辑分散(如
componentDidMount
和componentDidUpdate
可能写重复逻辑); - 高阶组件(HOC)和 Render Props 导致的 “嵌套地狱”;
- 函数组件无法使用状态的限制。
- 类组件的生命周期逻辑分散(如
12. useState
的工作原理是什么?如何实现复杂状态管理?
解析:
-
原理:
useState(initialState)
返回[state, setState]
,其中state
是当前状态,setState
是更新函数。React 通过闭包保存状态,每次调用setState
会触发组件重渲染,并更新闭包中的状态。 -
复杂状态管理:
- 多个
useState
:拆分独立状态(推荐,逻辑清晰); useReducer
:处理多状态联动(如表单多字段验证);- 注意:
setState
是合并更新(对象状态需手动展开,如setState(prev => ({...prev, name: 'new'}))
)。
- 多个
13. useEffect
的依赖数组有什么作用?空依赖数组代表什么?
解析:
-
useEffect(effect, deps)
中,deps
是依赖数组,用于控制effect
的执行时机:- 若
deps
省略:每次组件重渲染都会执行effect
和清理函数; - 若
deps
为空[]
:仅在组件挂载后执行一次effect
,卸载时执行清理函数(类似componentDidMount
和componentWillUnmount
); - 若
deps
有值:仅当数组中任意值变化时,才执行effect
(先执行上一次的清理函数,再执行新的effect
)。
- 若
-
常见错误:依赖数组遗漏依赖(如
effect
中使用了state
/props
但未加入deps
),会导致effect
捕获旧值(闭包问题)。
14. 什么是useRef
?它与createRef
有什么区别?
解析:
-
useRef(initialValue)
返回一个可变的ref
对象({current: initialValue}
),特点:ref.current
的变化不会触发组件重渲染;- 可保存 DOM 节点(如
input
)或跨渲染周期的变量(如定时器 ID)。
-
与
createRef
的区别:useRef
是 Hooks,仅用于函数组件,会在每次渲染时返回同一个对象(持久化);createRef
用于类组件,每次渲染会创建新对象。
15. useCallback
和useMemo
的作用是什么?有什么区别?
解析:
-
共同点:均用于性能优化,避免不必要的计算或重渲染。
-
区别:
useCallback(fn, deps)
:缓存函数(返回记忆化的函数),避免函数引用变化导致子组件(接收该函数为 props)重渲染。useMemo(valueFactory, deps)
:缓存计算结果(返回记忆化的值),避免每次渲染重复执行昂贵计算(如大数据排序)。
-
使用场景:
- 传递给子组件的回调函数 →
useCallback
; - 复杂计算的结果 →
useMemo
。
- 传递给子组件的回调函数 →
16. 什么是 Hooks 的闭包陷阱?如何避免?
解析:
-
闭包陷阱:Hooks 依赖闭包保存状态,若
effect
/useCallback
等的依赖数组未正确设置,会导致它们捕获旧的状态 /props,而非最新值。
例:jsx
const [count, setCount] = useState(0); useEffect(() => { setInterval(() => { console.log(count); // 始终打印0,因捕获了初始count }, 1000); }, []); // 依赖为空,effect只执行一次
-
避免方式:
- 将依赖加入依赖数组(如
[count]
); - 若依赖频繁变化,用
useRef
保存最新值(绕过闭包):jsx
const countRef = useRef(count); useEffect(() => { countRef.current = count; }, [count]); // 实时更新ref useEffect(() => { setInterval(() => { console.log(countRef.current); }, 1000); }, []);
- 将依赖加入依赖数组(如
17. useReducer
的使用场景是什么?与useState
相比有什么优势?
解析:
-
useReducer(reducer, initialState)
接收一个 reducer 函数和初始状态,返回[state, dispatch]
,通过dispatch(action)
触发状态更新(类似 Redux)。 -
适用场景:
- 状态逻辑复杂(多状态联动,如表单验证、购物车加减);
- 状态更新依赖前一个状态(
reducer
中可安全获取prevState
); - 需要预测状态变化(reducer 是纯函数,便于测试)。
-
与
useState
对比:- 优势:逻辑集中(reducer 函数统一管理更新),避免多个
setState
分散; - 劣势:简单状态用
useState
更简洁。
- 优势:逻辑集中(reducer 函数统一管理更新),避免多个
18. useContext
与useReducer
结合使用有什么好处?
解析:
-
结合后可实现 “轻量级状态管理”,无需引入 Redux:
- 用
useReducer
管理复杂状态和更新逻辑; - 用
useContext
将state
和dispatch
传递给子组件,避免 props 层级传递。
- 用
-
示例:
jsx
// 1. 创建Context const StoreContext = createContext(); // 2. 定义reducer const reducer = (state, action) => {/* ... */}; // 3. 父组件提供状态 const Parent = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> <Child /> </StoreContext.Provider> ); }; // 4. 子组件消费 const Child = () => { const { state, dispatch } = useContext(StoreContext); return <button onClick={() => dispatch({ type: 'ADD' })}>{state.count}</button>; };
19. 自定义 Hooks 的定义和使用规则是什么?
解析:
-
自定义 Hooks 是封装复用逻辑的函数,命名以
use
开头(如useWindowSize
),内部可调用其他 Hooks。 -
规则:
- 必须以
use
开头(React 通过命名检测 Hooks 调用位置,确保只在函数组件 / 自定义 Hooks 中调用); - 内部可调用其他 Hooks(如
useState
、useEffect
),但需遵循 Hooks 调用规则(不能在条件、循环中调用)。
- 必须以
-
示例(监听窗口大小):
jsx
function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return size; }
20. useLayoutEffect
与useEffect
的区别是什么?
解析:
-
执行时机:
useEffect
:在 DOM 渲染完成后异步执行(不阻塞浏览器绘制);useLayoutEffect
:在 DOM 更新后、浏览器绘制前同步执行(阻塞绘制)。
-
使用场景:
useEffect
:大多数场景(如数据请求、事件监听);useLayoutEffect
:需在绘制前修改 DOM(如避免布局抖动,如计算元素位置后立即调整)。
-
注意:
useLayoutEffect
可能导致性能问题(阻塞 UI),非必要不使用。
21. 如何用 Hooks 实现类组件的getDerivedStateFromProps
?
解析:
getDerivedStateFromProps
用于从 props 派生 state(类组件中),Hooks 中可通过以下方式实现:
-
直接在函数组件中根据 props 计算状态(适用于简单场景):
jsx
const [state, setState] = useState(initialState); useEffect(() => { setState(prev => computeStateFromProps(props, prev)); // 当props变化时更新state }, [props]); // 依赖props
-
注意:避免 “派生状态” 过度使用(可能导致状态来源混乱),优先考虑:
- 直接使用 props(无需保存为 state);
- 用
useMemo
缓存计算结果(而非存入 state)。
22. 如何用 Hooks 实现防抖 / 节流?
解析:
结合useRef
(保存定时器)和useCallback
(缓存函数)实现:
-
防抖(示例):
jsx
function useDebounce(fn, delay) { const timerRef = useRef(null); return useCallback((...args) => { if (timerRef.current) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { fn.apply(this, args); }, delay); }, [fn, delay]); } // 使用: const debouncedSearch = useDebounce((value) => { console.log('搜索:', value); }, 500);
-
节流(示例):
jsx
function useThrottle(fn, interval) { const lastTimeRef = useRef(0); return useCallback((...args) => { const now = Date.now(); if (now - lastTimeRef.current >= interval) { fn.apply(this, args); lastTimeRef.current = now; } }, [fn, interval]); }
23. useImperativeHandle
的作用是什么?
解析:
useImperativeHandle(ref, createHandle, deps)
用于自定义暴露给父组件的 ref 方法,避免父组件直接操作子组件 DOM(封装性更好)。
- 示例:
jsx
// 子组件 const Child = forwardRef((props, ref) => { const inputRef = useRef(null); // 自定义ref暴露的方法 useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, clear: () => { inputRef.current.value = ''; } }), []); return <input ref={inputRef} />; }); // 父组件 const Parent = () => { const childRef = useRef(null); return ( <div> <button onClick={() => childRef.current.focus()}>聚焦</button> <Child ref={childRef} /> </div> ); };
24. useDebugValue
的作用是什么?
解析:
useDebugValue(value, formatter)
用于在 React DevTools 中自定义 Hooks 的显示名称和值,方便调试。
- 示例:
jsx
function useUser(id) { const [user, setUser] = useState(null); useEffect(() => { fetch(`/api/user/${id}`).then(res => setUser(res.data)); }, [id]); // 在DevTools中显示为“User: 用户名”(而非原始值) useDebugValue(user, user => user ? `User: ${user.name}` : 'Loading...'); return user; }
25. 为什么 Hooks 不能在条件语句、循环或嵌套函数中调用?
解析:
React 通过Hooks 调用顺序来关联组件状态(每次渲染时,Hooks 的调用顺序必须一致)。若在条件 / 循环中调用,会导致:
- 调用顺序混乱,React 无法正确匹配 Hooks 与状态,引发状态错乱。
例(错误):
jsx
function Component() {
if (condition) {
const [a, setA] = useState(0); // 条件中调用,可能导致后续Hooks顺序错误
}
const [b, setB] = useState(1); // 若condition为false,此处会被视为第一个Hooks,与上次渲染顺序不一致
}
三、状态管理(26-40 题)
26. Redux 的核心概念是什么?工作流程是怎样的?
解析:
-
核心概念:
Store
:保存整个应用的状态树(唯一);Action
:描述状态变化的普通对象(必须有type
字段,如{ type: 'ADD', payload: 1 }
);Reducer
:纯函数((state, action) => newState
),根据 action 更新 state;Dispatch
:发送 action 的方法(store.dispatch(action)
)。
-
工作流程:
- 组件通过
dispatch
发送 action; - Redux 调用 reducer,根据 action 计算新 state;
- Store 更新 state,触发组件重新渲染(通过
subscribe
监听)。
- 组件通过
27. Redux 为什么要求 reducer 是纯函数?
解析:
纯函数的特性:
- 相同输入必返回相同输出;
- 无副作用(不修改参数、不调用 API、不操作 DOM 等)。
Redux 依赖纯函数的特性实现:
- 可预测性:状态变化完全由 action 和当前 state 决定,便于调试和测试;
- 可回溯:纯函数无副作用,支持时间旅行(如 Redux DevTools 的状态回退);
- 一致性:避免意外修改 state(reducer 必须返回新 state,而非修改原 state)。
28. Redux 中间件的作用是什么?常用的中间件有哪些?
解析:
-
作用:扩展 Redux 的功能,处理异步 action(如 API 请求)、日志记录、错误处理等(Redux 默认只支持同步 action)。
-
常用中间件:
redux-thunk
:允许 action 是函数(而非对象),在函数中异步 dispatch action(如(dispatch) => { fetch().then(res => dispatch(action)) }
);redux-saga
:用 generator 函数处理复杂异步逻辑(如取消请求、重试、竞态处理);redux-logger
:打印 action 和 state 变化日志,便于调试。
29. redux-thunk
和redux-saga
的区别是什么?如何选择?
解析:
维度 | redux-thunk | redux-saga |
---|---|---|
语法 | 基于函数(简单直观) | 基于 generator(略复杂) |
异步控制 | 弱(依赖 Promise 链式调用) | 强(支持取消、重试、节流) |
错误处理 | try/catch 或 Promise.catch | try/catch(generator 特性) |
测试难度 | 较简单 | 较复杂(需处理 generator) |
适用场景 | 简单异步(如单次 API 请求) | 复杂异步(如长轮询、并发) |
选择:简单场景用thunk
,复杂异步逻辑用saga
。
30. Redux 的combineReducers
有什么作用?
解析:
-
作用:将多个 reducer 合并为一个根 reducer(因 Redux store 只能有一个根 reducer),实现状态树的模块化拆分。
-
示例:
jsx
// 拆分reducer const userReducer = (state = {}, action) => {/* ... */}; const cartReducer = (state = [], action) => {/* ... */}; // 合并 const rootReducer = combineReducers({ user: userReducer, cart: cartReducer }); // store的state结构为 { user: {...}, cart: [...] }
31. React-Redux 的connect
函数作用是什么?mapStateToProps
和mapDispatchToProps
的作用?
解析:
-
connect
:将 Redux store 与 React 组件连接,使组件能获取 state 和 dispatch action。 -
mapStateToProps(state, ownProps)
:- 接收 store 的 state 和组件自身 props,返回一个对象(作为 props 传入组件),使组件能访问 Redux 状态。
- 例:
(state) => ({ count: state.count })
→ 组件可通过this.props.count
访问。
-
mapDispatchToProps(dispatch, ownProps)
:- 接收 dispatch 方法,返回一个对象(包含函数,作为 props 传入组件),使组件能 dispatch action。
- 例:
(dispatch) => ({ add: () => dispatch({ type: 'ADD' }) })
→ 组件可通过this.props.add()
触发 action。
32. Redux Toolkit(RTK)解决了 Redux 的哪些问题?
解析:
Redux 原生使用繁琐(需手动写 action type、reducer、immer 处理不可变数据等),RTK 是官方推荐的工具集,解决:
- 样板代码过多:
createSlice
自动生成 action type 和 creator; - 不可变数据处理复杂:内置
immer
,允许 “直接修改” state(实际生成新 state); - 异步逻辑繁琐:
createAsyncThunk
简化异步 action 创建; - store 配置复杂:
configureStore
默认集成redux-thunk
、开发者工具等。
示例(createSlice
):
jsx
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; } // 直接修改(immer处理)
}
});
export const { increment } = counterSlice.actions; // 自动生成action creator
export default counterSlice.reducer;
33. 什么是 Redux 的不可变性(Immutability)?为什么重要?
解析:
-
不可变性:state 一旦创建,不能直接修改,只能返回新的 state 对象(如
return {...state, count: state.count + 1}
)。 -
重要性:
- 便于追踪状态变化(对比新旧 state 是否引用相同,即可判断是否变化);
- 支持时间旅行(Redux DevTools 需保存每一次 state 快照);
- 避免意外副作用(防止多个地方修改同一 state 导致的混乱)。
34. MobX 的核心概念是什么?与 Redux 相比有什么区别?
解析:
-
MobX 核心概念:
Observable
:标记可观察状态(如observable({ count: 0 })
);Action
:修改 observable 状态的函数(需用action
标记,确保状态变更可追踪);Reaction
:响应状态变化的副作用(如autorun
、reaction
)。
-
与 Redux 的区别:
维度 Redux MobX 范式 函数式(单向数据流) 面向对象(响应式) 状态修改 必须通过 action 和 reducer 直接修改(action 标记) 样板代码 多(需定义 action、reducer) 少(简洁直观) 可预测性 高(严格规范) 较低(灵活但易混乱)
35. 什么是 Recoil?它解决了什么问题?
解析:
Recoil 是 Facebook 推出的状态管理库,专为 React 设计,解决:
- Context 的性能问题(频繁更新导致所有消费组件重渲染);
- Redux 的样板代码冗余。
核心概念:
Atom
:可共享的状态单元(类似 Redux 的 state 片段,如const countAtom = atom({ key: 'count', default: 0 })
);Selector
:基于 atom 或其他 selector 的派生状态(纯函数计算,如const doubleCount = selector({ key: 'double', get: ({get}) => get(countAtom) * 2 })
);- 组件通过
useRecoilState
/useRecoilValue
消费状态,仅当依赖的 atom/selector 变化时才重渲染。
36. Zustand 的特点是什么?适合什么场景?
解析:
Zustand 是轻量级状态管理库,特点:
- 极简 API(无需 Provider 包裹);
- 支持中间件(如持久化、日志);
- 与 React Hooks 无缝集成。
使用示例:
jsx
import create from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}));
// 组件中使用
const Component =
编辑
分享
生成一份包含100道React中高级面试题及解析的文档
React中如何优化组件的性能?
分享一些关于React状态管理的实际项目经验
深度思考: 自动
技能