以下为React面试进阶篇考察点总结,具体知识点不会太详细,主要梳理面试核心考察点,为面试做准备。
一、核心机制
1.合成事件机制
1.设计目的与核心原理
1.跨浏览器一致性
- 统一不同浏览器的事件处理接口(如
event.target
的行为) - 修复浏览器兼容性问题(如IE事件模型差异)
2.性能优化
- 事件委托:将事件绑定到根节点(React 17+ 为应用根DOM),而非每个子元素
- 事件池化(
Event Pooling
):复用事件对象,减少内存开销
3.扩展能力
- 支持自定义事件类型(如
onDoubleClick
) - 实现高级功能(如事件优先级调度)
2.事件委托机制演进
React 版本 | 委托层级 | 核心变化 |
---|---|---|
16.x 及之前 | 所有事件委托到 document | 多 React 应用共存时事件可能冲突 |
17.x 及之后 | 委托到应用根 DOM 节点 | 隔离不同 React 版本的事件系统,避免全局污染 |
// React 17+ 事件委托结构
const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);
// 所有 React 事件监听器绑定到 rootNode 而非 document
3.合成事件对象
1.核心属性
interface SyntheticEvent {
nativeEvent: Event; // 原生事件对象
currentTarget: DOMElement; // 事件绑定的 React 元素
target: DOMElement; // 触发事件的 DOM 元素
type: string; // 事件类型(如 'click')
isDefaultPrevented(): boolean;
isPropagationStopped(): boolean;
persist(): void; // 禁用事件池化
}
2.事件池化示例
function handleClick(event) {
// ❌ 错误:异步访问事件属性
setTimeout(() => {
console.log(event.target); // null
}, 100);
// ✅ 正确:保留事件引用
event.persist();
setTimeout(() => {
console.log(event.target); // 正常输出
}, 100);
}
4.事件处理流程
1.事件注册
- React初始化时注册所有支持的事件(如
onClick,onChange
) - 通过
EventListener
在根节点监听原生事件
2.事件触发
原生事件触发 → 根节点捕获事件 → React 生成 SyntheticEvent → 收集事件监听器 → 按组件树冒泡/捕获顺序执行
3.执行顺序
- 捕获阶段:
父组件 onClickCapture -> 子组件 onClickCapture
- 冒泡阶段:
子组件 onClick -> 父组件 onClick
与原生事件交互
1.混合使用场景
useEffect(() => {
const handleNativeClick = (e) => {
console.log('原生事件触发');
};
document.addEventListener('click', handleNativeClick);
return () => {
document.removeEventListener('click', handleNativeClick);
};
}, []);
// React 事件处理
const handleReactClick = (e) => {
console.log('合成事件触发');
e.stopPropagation(); // 阻止 React 事件冒泡
};
2.执行顺序
原生事件(捕获) → 原生事件(目标) → React 事件(捕获) → React 事件(目标) → React 事件(冒泡) → 原生事件(冒泡)
6.常见问题与解决方案
1.事件阻止传播失败
- 问题:
e.stopPropagation()
仅阻止React事件传播,不影响原生事件 - 方案:同时阻止原生事件传播
const handleClick = (e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
};
2.事件监听器性能优化
- 避免在渲染时创建新函数:使用
useCallback
函数缓存
const handleClick = useCallback((e) => { /* ... */ }, []);
7.高频面试题
1.为什么React不直接将事件绑定在元素上?
- 事件委托减少内存占用,动态更新组件时无需重新绑定事件
2.合成事件和原生事件的区别
- 合成事件跨浏览器统一行为
- 原生事件直接操作DOM,无React抽象层
3.如何全局阻止React事件
- 劫持根节点事件监听
// 危险操作!仅用于演示
document.getElementById('root').addEventListener('click', e => {
e.stopImmediatePropagation();
}, true);
2.组件更新触发条件与渲染优化
1.组件更新触发条件
组件重新渲染的根本原因是组件状态或数据依赖发生变化,具体触发场景如下:
触发条件 | 说明 |
---|---|
State 变化 | 组件内部 useState /useReducer /this.setState 更新状态 |
Props 变化 | 父组件重新渲染导致传入的 props 值变化 |
Context 更新 | 组件订阅的 Context 数据发生变更 |
父组件重新渲染 | 即使子组件的 props 未变化,父组件渲染仍可能导致子组件重新渲染(默认行为) |
强制更新 | 类组件调用 this.forceUpdate() |
Hooks 依赖变化 | useEffect /useMemo /useCallback 的依赖数组元素变更 |
2.React渲染机制核心原理
1.渲染流程
触发更新 -> 生成虚拟DOM -> Diff算法比较 -> 确定DOM更新范围 -> 提交到真实DOM
2.协调(Reconciliation)策略
- 树对比:仅对比同层级节点,时间复杂度O(n)
- Key值优化:列表项使用key帮助React识别元素移动/复用
3.渲染优化策略与实践
1.避免不必要的父组件渲染
- 场景:父组件状态变化导致所有子组件重新渲染
- 优化方案:
// 父组件:将状态隔离到独立子组件
function Parent() {
return (
<>
<ExpensiveChild />
<StateContainer /> // 将易变状态抽离
</>
);
}
2.组件自身渲染控制
- 类组件
class MyComponent extends React.PureComponent { // 自动浅比较 props/state
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps); // 手动控制更新条件
}
}
- 函数组件
const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
return prevProps.id === nextProps.id; // 自定义 props 比较
});
3.精细化Hooks使用
- 缓存计算结果
const expensiveValue = useMemo(() => computeValue(a, b), [a, b]);
- 稳定函数引用
const handleClick = useCallback(() => {
// 依赖 a 但保持引用稳定
}, [a]);
- 按需订阅Context
const value = useContextSelector(MyContext, v => v.requiredField);
4.列表渲染优化
- 虚拟滚动:使用
react-window
或react-virtualized
import { FixedSizeList as List } from 'react-window';
<List height={600} itemSize={35} itemCount={1000}>
{({ index, style }) => <div style={style}>Row {index}</div>}
</List>
- key值策略
// ❌ 错误:使用索引作为 key
{items.map((item, index) => <Item key={index} />)}
// ✅ 正确:使用唯一标识
{items.map(item => <Item key={item.id} />)}
4.高频面试题
1.为什么父组件更新会导致所有子组件渲染?如何避免?
- react默认采用"
render and diff
"策略,使用React.memo/shouldComponentUpdate
阻断无效更新
2.useMemo一定能提升性能吗?使用场景是什么?
- 不一定。仅当计算开销大且稳定时使用,否则可能因依赖数组计算反增开销
3.如何优化Context引起的渲染?
- 拆分多个Context
- 使用
useContextSelector
按需订阅
const ThemeButton = () => {
const theme = useContextSelector(ThemeContext, v => v.color);
return <button style={{ color: theme }}>Submit</button>;
};
4.函数组件每次渲染都会创建新函数,如何避免传递新props?
- 使用useCallback缓存函数引用
const handleSubmit = useCallback(() => { /*...*/ }, [deps]);
6.性能优化法则
- 优先解决重复渲染问题:使用
React DevTools Profiler
定位关键路径 - 避免过早优化:只在性能瓶颈出现时实施优化
- 保持组件纯净:减少渲染过程中的副作用操作
- 控制渲染范围:使用
children props
阻断无关更新
// 父组件
<Layout>
<StaticContent /> {/* 内容不会因 Layout 更新而重渲染 */}
</Layout>
// Layout 组件
function Layout({ children }) {
const [state, setState] = useState();
return <div>{children}</div>;
}
3.Hooks核心原理
1.Hooks的设计目标
- 逻辑复用:解决类组件中高阶组件(HOC)和Render Props的嵌套地狱问题
- 简化组件:告别this绑定和生命周期方法的分散逻辑
- 函数式优先:拥抱函数式编程范式,提升代码可预测性
- 渐进式升级:兼容现有组件,无需重写即可逐步迁移
2.核心原理
1.闭包与链表存储
- 存储结构:Hooks数据存储在Fiber节点的
memoizedState
属性中,通过单向链表管理 - 执行顺序依赖:Hooks调用顺序在每次渲染中必须严格一致(链表顺序不可变)
- 闭包陷阱:每个Hooks闭包捕获当次渲染的props/state快照
// Fiber 节点结构示意
const fiber = {
memoizedState: {
memoizedState: '状态值', // useState 的状态
next: { // 下一个 Hook
memoizedState: [], // useEffect 的依赖数组
next: null
}
}
};
2.调度机制
- 优先级调度:Hooks更新请求会被
Scheduler
模块根据优先级(Immediate/UserBlocking/Normal
)排队处理 - 批量更新:React自动合并多个setState调用,减少渲染次数
3.核心Hooks原理解析
1.useState
- 存储结构:
[state, dispatchAction]
存储在链表节点中 - 更新触发:调用
dispatchAction
会创建更新对象,触发重新渲染 - 异步更新:状态更新在下次渲染时生效(遵循批量更新原则)
// 简化实现
function useState(initial){
const fiber = getCurrentFiber();
const hook = fiber.memorizedState?.isStateHook ? fiber.memoizedState : { memouzedState: initial, next: null };
const dispatch = (action) => {
const update = { action };
hook.queue.push(update);
scheduleWork(); // 触发重新渲染
};
return [hook.memoizedState, dispatch];
}
2.useEffect
- 依赖对比:使用浅比较(Object.is)判断依赖数组变化
- 执行时机:在浏览器完成布局与绘制后异步执行(避免阻塞渲染)
- 清理机制:返回的清理函数会在下次effect执行前或组件卸载前执行
// 依赖对比伪代码
function areDepsEqual(prevDeps, nextDeps) {
if (!prevDeps || !nextDeps) return false;
for (let i = 0; i < prevDeps.length; i++) {
if (!Object.is(prevDeps[i], nextDeps[i])) return false;
}
return true;
}
3.useRef
- 跨渲染存储:ref对象在组件生命周期内保持不变
- 直接修改:
ref.current
的修改不会触发重新渲染
// 简化实现
function useRef(initialValue) {
const ref = { current: initialValue };
return useMemo(() => ref, []); // 永远返回同一引用
}
4.Hooks规则的本质
1.为什么必须顶层调用?
- 链表顺序依赖:Hooks的存储依赖调用顺序,条件语句会破坏链表结构
2.为什么只能在函数组件中使用?
- Fiber关联:Hooks需要绑定当前组件的Fiber节点,普通函数无此上下文
5.闭包陷阱与解决方案
1.过期闭包问题
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // 始终捕获初始值 0
}, 1000);
return () => clearInterval(timer);
}, []); // ❌ 错误:空依赖数组
// 正确方案:使用函数式更新或添加依赖
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // ✅ 获取最新值
}, 1000);
return () => clearInterval(timer);
}, []);
}
2.解决方案
- 函数式更新:
setState(c => c + 1)
- 依赖数组精准化:确保所有依赖项被正确声明
- useRef穿透闭包:通过ref访问最新值
const countRef = useRef(count);
countRef.current = count;
6.高频面试题
1.Hooks如何实现状态隔离?
- 每个组件实例的Fiber节点维护独立的Hooks链表,相同组件不同实例互不影响
2.自定义Hook的本质是什么?
- 将多个内置Hooks组合为可复用的逻辑单元,遵循Hooks规则
3.为什么useEffect的依赖数组是浅比较
- 性能优化考量,深比较成本过高。复杂对象应使用useMemo稳定引用
4.useMemo/useCallback如何避免重复计算?
- 通过依赖数组决定是否重新计算,引用稳定时返回缓存值
const memoValue = useMemo(() => compute(a), [a]);
const memoFn = useCallback(() => action(a), [a]);
7.性能优化策略
优化手段 | 实现方式 | 适用场景 |
---|---|---|
精细化依赖数组 | 确保依赖数组只包含必要变量 | 所有 Hooks |
状态提升 | 将状态提升到父组件或 Context | 多组件共享状态 |
惰性初始 state | useState(() => expensiveInit()) | 初始值计算成本高时 |
批量更新 | unstable_batchedUpdates (React 18 自动) | 合并多次状态更新 |
4.常用Hooks
1.基础Hooks
1.useState
- 作用:为函数组件添加状态管理能力
- 使用场景:组件内部的状态管理
const [count, setCount] = useState(() => 0); // 惰性初始化
const increment = () => setCount(prev => prev + 1);
- 注意:
- 状态更新是异步的,连续调用会被合并
- 复杂对象建议使用useReducer
2.useEffect
- 作用:处理副作用(数据请求、DOM操作、订阅)
- 生命周期映射:
componentDidMount -> 依赖数组为空 []
componentDidUpdate -> 指定依赖项 [dep]
componentWillUnmount -> 返回清理函数
useEffect(() => {
const subscription = props.source.subscribe();
return () => subscription.unsubscribe(); // 清理
}, [props.source]);
- 注意
- 默认在浏览器完成渲染后异步执行
- 使用
useLayoutEffect
处理同步DOM操作
3.useContext
- 作用:跨组件层级传递数据
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>{theme}</div>;
}
- 优化:使用memo防止无关组件更新
2.性能优化Hooks
1.useMemo
- 作用:缓存计算结果,避免重复计算
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
- 注意:
- 依赖数组引用,避免子组件无效渲染
- 不应用于有副作用的操作
2.useCallback
- 作用:缓存函数引用,避免子组件无效渲染
const handleSubmit = useCallback(() => {
submitData(name);
}, [name]);
- 等价写法:
const memoizedFn = useMemo(() => () => submitData(name), [name]);
3.React.memo
- 作用:浅比较props变化,阻止无效渲染
const MemoComponent = React.memo(Child, (prev, next) => {
return prev.id === next.id; // 自定义比较函数
});
进阶Hooks
1.useReducer
- 作用:复杂状态逻辑管理(类似Redux)
const initialState = { count: 0 };
function reducer(state, action) {
switch(action.type) {
case 'increment':
return { count: state.count + 1 };
default: return state;
}
}
const [count, dispatch] = useReducer(reducer, initialState);
- 优势:适合多状态联动或逻辑状态复杂的场景
2.useRef
- 作用:
- 访问DOM元素
- 保存可变值(不触发渲染)
const inputRef = useRef();
useEffect(() => inputRef.current.focus(), []);
// 保存上一次 props 值
const prevCount = useRef(count);
useEffect(() => {
prevCount.current = count;
});
3.useLayoutEffect
- 作用:同步执行副作用,在浏览器绘制前完成DOM修改
- 场景:测量DOM布局、同步样式调整
useLayoutEffect(() => {
const width = divRef.current.offsetWidth;
setWidth(width); // 同步更新状态
}, []);
4.特殊场景Hooks
1.useImperativeHandle
- 作用:自定义暴露给父组件的实例方法
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}));
return <input ref={inputRef} />;
});
2.useDebugValue
- 作用:在React开发中工具中显示自定义hook标签
function useFriendStatus() {
const [isOnline] = useState(null);
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
5.高频面试题
1.useEffect和useLayoutEffect的区别?
- useEffect异步执行(不阻塞渲染)
- useLayoutEffect同步执行(在DOM更新后,浏览器绘制前)
2.如何避免useEffect的无限循环?
- 正确设置依赖数组
- 使用useCallback/useMemo稳定引用
3.useMemo和React.memo的区别?
- useMemo缓存值
- React.memo缓存组件渲染结果
5.虚拟DOM Diff算法原理
1.核心设计思想
React的Diff算法基于两个核心假设,以O(n)时间复杂度完成树结构的高效比较
- 类型差异假设:不同类型的元素会生成不同的树结构,直接替换整个子树
- Key稳定性假设:通过key标识相同层级的元素是否可复用
2.分层对比策略
React采用逐层递归
比较,但不会跨层级追踪节点变化:
旧树:<div> 新树:<section>
<A/> <A/>
<B/> <B/>
</div> </section>
处理逻辑:发现 div → section 类型不同,直接销毁整个 div 子树,重建 section 子树
3.同层级节点比较
当父节点类型相同时,递归比较其他子节点
1.列表节点对比优化
- 场景:动态列表项的顺序变化(增删、排序)
- 策略:使用唯一的key标识元素身份,最小化移动操作
// 旧列表
<ul>
<li key="a">A</li>
<li key="b">B</li>
</ul>
// 新列表(B 移动到 A 前面)
<ul>
<li key="b">B</li>
<li key="a">A</li>
</ul>
// React 处理:仅移动 DOM 节点,而非销毁重建
2.无key列表的默认行为
- 使用索引作为key:可能导致错误复用(如列表项内容变化但位置不变)
- 性能陷阱:列表顺序变化时,索引变化导致不必要的重新渲染
4.元素类型处理
1.类型相同
- 更新属性:仅修改变化的属性(如className、style)
- 递归比较子节点:继续对比子元素
2.类型不同
- 销毁旧子树:触发旧组件
componentWillUnmount
- 创建新子树:触发新组件
constructor -> render -> componentDidMount
5.Diff算法步骤分解
1.根节点对比
- 类型不同 -> 整树替换
- 类型相同 -> 进入属性比较
2.属性更新
// 伪代码示例
function updateDOMProperties(domElement, prevProps, nextProps) {
// 移除旧属性
Object.keys(prevProps).forEach(propName => {
if (!nextProps.hasOwnProperty(propName)) {
domElement.removeAttribute(propName);
}
});
// 设置新属性
Object.keys(nextProps).forEach(propName => {
if (prevProps[propName] !== nextProps[propName]) {
domElement.setAttribute(propName, nextProps[propName]);
}
});
}
3.子节点递归对比
- 策略:双指针遍历新旧子节点列表
- 操作类型:
- INSERT:新增节点
- MOVE:移动已有节点
- REMOVE:删除废旧节点
key的优化机制
场景 | 无 Key | 有 Key |
---|---|---|
列表项顺序变化 | 索引变化导致全部重新渲染 | 识别移动项,仅调整 DOM 顺序 |
列表中间插入项 | 后续项索引变化触发多节点更新 | 仅插入新节点,后续项无变化 |
删除中间项 | 后续项索引变化触发多节点更新 | 仅删除目标节点 |
7.性能优化实践
1.key的使用原则
场景 | 无 Key | 有 Key |
---|---|---|
列表项顺序变化 | 索引变化导致全部重新渲染 | 识别移动项,仅调整 DOM 顺序 |
列表中间插入项 | 后续项索引变化触发多节点更新 | 仅插入新节点,后续项无变化 |
删除中间项 | 后续项索引变化触发多节点更新 | 仅删除目标节点 |
7.性能优化实践
1.key的使用原则
- 使用数据唯一的标识(如id)
- 避免随机数或索引(不稳定)
2.避免跨层级移动节点
- 修改父节点类型会导致子树重建
3.减少顶层节点类型变化
- 保持稳定的组件结构
4.复杂列表优化
- 虚拟滚动(如reac-window)
- 分页加载
高频面试题
1.为什么列表必须使用key?
- 帮助React识别元素身份,在顺序变化时高效复用DOM节点,避免以下问题:
- 不必要的子组件重新渲染
- 表单元素状态错乱(如输入框内容错位)
2.如何强制组件重新挂载?
- 改变key值触发销毁/重建
<UserProfile key={user.id} user={user} />
3.Diff算法能完全避免DOM操作吗?
- 不能。目的是最小化操作次数,但无法消除必要的更新(如数据变化必然导致DOM修改)
6.Fiber
1.Fiber的诞生背景
1.旧版协调算法瓶颈
- 递归不可中断:同步遍历整个虚拟DOM树,长时间占用主线程
- 卡顿问题:复杂组件树更新导致掉帧(如大型列表、动画场景)
2.目标
- 实现增量渲染,支持异步可中断的更新
2.Fiber核心设计思想
1.时间切片
- 将渲染任务拆分为多个小任务(Fiber节点)
- 利用浏览器空闲时段(
requestIdleCallback
)分片执行
2.优先级调度
- 用户交互(如输入)优先于数据更新(如API响应)
3.可恢复工作单元
- 保存中间状态,允许暂停/恢复渲染流程
3.Fiber节点数据结构
每个Fiber节点对应一个组件实例或DOM节点,包含以下核心属性:
属性 | 类型 | 作用 |
---|---|---|
type | String/Object | 组件类型(如 'div' 、函数组件引用) |
stateNode | Object | 对应的 DOM 节点或类组件实例 |
child | Fiber | 第一个子节点 |
sibling | Fiber | 下一个兄弟节点 |
return | Fiber | 父节点 |
pendingProps | Object | 新传入的 props |
memoizedProps | Object | 上一次渲染使用的 props |
memoizedState | Object | 上一次渲染后的 state(如 Hooks 链表) |
effectTag | Number | 标记副作用类型(如 Placement 、Update 、Deletion ) |
alternate | Fiber | 指向当前 Fiber 的镜像(用于 Diff 比较) |
4.Fiber双缓冲机制
React维护两颗Fiber树已确保更新无冲突
- Current Tree:当前已渲染的UI对应的Fiber树
- WorkInProgress Tree:正在构建的新Fiber树
- 切换流程:更新完成后,
WorkInProgress Tree
树变为Current树
初始渲染:
Current Tree: null
WorkInProgress Tree: → 构建完成 → 提交后成为 Current Tree
更新阶段:
Current Tree ←→ WorkInProgress Tree(复用或新建节点)
3.Fiber渲染
1.协调阶段
- 目标:生成副作用列表,不修改DOM
- 过程:
- 递阶段:调用render生成子节点,标记变化
- 归阶段:向上回溯收集副作用
- 可中断:根据剩余时间片暂停/恢复遍历
2.提交阶段
- 目标:同步执行所有DOM变更
- 过程:
- Before Mutation:调用
getSnapshotBeforeUpdate
- Mutation:执行DOM增删改
- Layout:调用
useLayoutEffect
和componentEDidMount/Update
- Before Mutation:调用
- 不可中断:避免中间状态导致UI不一致
6.优先级调度模型
优先级 | 对应场景 |
---|---|
ImmediatePriority | 同步任务(如 flushSync ) |
UserBlockingPriority | 用户交互(点击、输入) |
NormalPriority | 数据更新、网络响应 |
LowPriority | 过渡更新(Concurrent 模式) |
IdlePriority | 空闲时执行的任务 |
- 调度策略:高优先级任务可中断低优先级任务
7.Fiber对生命周期的影响
1.废弃生命周期:
componentWillMount、componentWillReceiveProps、componentWillUpdate
- 原因:异步可渲染可能导致多次调用,引发副作用错误
2.新增API
getDerivedStateFromProps
(静态方法,替代componentWillReceiveProps
)getSnapshotBeforeUpdate
(替代componentWillUpdate
)
8.Fiber与并发模式
1.并发特性
- useTranstion:标记非紧急更新,可被高优先级任务打断
- Suspense:等待异步数据加载时显示回退UI
2.启用方式
// 创建根节点时启用
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
9.高频面试题
1.Fiber如何实现可中断更新?
- 通过链表结构存储Fiber节点,记录遍历进度。每次处理一个Fiber后检查剩余时间片,不足时保存当前进度,将控制权交还浏览器
2.协调阶段和提交阶段的区别
- 协调阶段负责计算变更(可中断),提交阶段执行DOM操作(同步不可中断)
3.为什么需要双缓冲机制?
- 保证渲染过程中Current Tree始终完整可用,避免更新过程中出现UI不一致
二、状态管理
1.状态管理的核心
- 组件通信:跨层级组件共享数据
- 状态同步:多个组件依赖统一数据源
- 副作用管理:异步操作(API请求、定时器)与状态更新的协调
- 调试追踪:复杂应用中状态变更的可预测性
2.React内置解决方案
1.组件状态(useState/useReducer)
- 适用场景:组件内部私有状态(如表单输入、UI切换)
const [count, setCount] = useState(0);
// useReducer 处理复杂逻辑
const reducer = (state, action) => {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
default: return state;
}
};
const [state, dispatch] = useReducer(reducer, { count: 0 });
2.Context API
- 适用场景:中低频次更新的全局状态(如主题、用户身份)
// 拆分多个Context
const ThemeContext = createContext();
const UserContext = createContext();
// 使用memo防止子组件冲重渲染
const UserPanel = memo(() => {
const user = useContext(UserContext);
return <div>{user.name}</div>;
})
3.主流状态管理库对比
方案 | 核心模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
Redux | 单一 Store + Flux | 严格的数据流、强大的中间件生态 | 样板代码多、学习曲线陡峭 | 大型复杂应用、需要时间旅行调试 |
MobX | 响应式编程 | 代码简洁、自动追踪依赖 | 黑盒机制、过度渲染风险 | 中小型应用、快速开发 |
Recoil | 原子状态 + Selector | 细粒度控制、原生支持异步 | 较新、生态不成熟 | 需要局部状态优化的场景 |
Zustand | 轻量 Store | API 简单、无样板代码 | 功能相对基础 | 中小项目、替代部分 Redux 用例 |
Context + Hooks | 组合式状态 | 零依赖、React 原生支持 | 性能问题、缺乏中间件 | 简单全局状态管理 |
4.Redux核心原理与最佳实践
1.三大原则:
- 单一数据源:整个应用状态存储在一个Store中
- 只读State:通过Action修改意图
- 纯函数Reducer:接收旧State和Action,返回新State
2.现代Redux开发(Redux Toolkit)
// 创建Slice
const counterSlice = createSlice({
name: 'counter',
inintialState: 0,
reducers: {
increment: state => state + 1,
decrement: state => state - 1
}
});
// 配置Store
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// 使用Hooks连接组件
const Counter = () => {
const count = useSelector(state => state.counter);
const dispach = useDispatch();
return <button onClick={() => dispatch(counterSlice.actions.increment())}{count}</button>
}
3.中间件应用
// 处理异常逻辑(redux-thunk)
const fetchUser = () => async (dispatch) => {
dispatch({ type: 'USER_REQUEST' });
try {
const res = await api.getUser();
dispatch({ type: 'USER_SUCCESS', payload: res.data });
} catch (err) {
dispatch({ type: 'USER_FAILURE', error: err.message });
}
}
// 日志中间件
const logger = store => next => action => {
console.log('dispatching:', action);
let result = next(action);
console.log('next state:', store.getState());
return result;
}
5.状态管理选型
1.评估应用规模
- 小型应用:Context + useState/useReducer
- 中大型应用:Redux/Mobx
2.团队熟悉度
- 已有Redux经验:Redux Toolit
- 偏好响应式:Mobx
3.性能需求
- 高频更新:Recoil原子状态
- 复杂异步:Redux + Saga/Thunk
4.开发效率
- 快速迭代:Zustand/Jotai
- 长期维护:Redux(强约束性)
6.高频面试题
1.redux如何避免不必要的重新渲染?
- 使用
reselect
创建记忆化Selector
,避免重复计算 - 结合
React.memo
对组件进行浅比较
const selectUser = state => state.user;
const selectUserName = createSelector(
[selectUser],
(user) => user.name
);
2.Mobx的响应式原理
- 通过ES6
Proxy
或Object.defineProperty
追踪属性访问,自动建立观察者-被观察者关系,状态变更时触发依赖组件更新
3.如何解决Context API的性能问题?
- 拆分多个Context隔离变化
- 使用useMemo缓存Provider的value
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
4.状态管理的不可变性为何重要?
- 确保状态变更可预测,便于调试追踪
- 支持React的浅比较优化策略(如
shouldComponentUpdate
)
以上是React面试题的进阶篇内容,如有错误欢迎评论区指正,后续还会更新React高阶篇。