React Hooks详解
React Hooks 常考内容
React Hooks 是 React 16.8 引入的重要特性,用于在函数组件中使用状态和其他 React 特性。以下是面试中常考的核心内容:
基础 Hook
useState
: 用于管理组件内部状态,返回状态变量和更新状态的函数。useEffect
: 处理副作用(如数据请求、DOM 操作、订阅等),可以模拟生命周期方法。useContext
: 允许组件订阅 React 上下文,避免多层 props 传递。
进阶 Hook
useReducer
: 复杂状态逻辑管理,类似于 Redux 的 reducer 模式。useCallback
: 缓存函数,避免不必要的重新渲染。useMemo
: 缓存计算结果,优化性能。useRef
: 创建可变的引用对象(如访问 DOM 或保存变量)。useLayoutEffect
: 类似useEffect
,但同步执行,适用于 DOM 布局相关操作。
自定义 Hook
- 封装可复用的逻辑,遵循命名规则
useXxx
。
React Hooks 知识框架详解
核心机制与原理
- Hook 调用顺序:Hooks 必须在函数组件的顶层调用,不可嵌套在条件或循环中。React 依赖调用顺序来跟踪状态。
- 闭包与依赖数组:Hooks 依赖 JavaScript 闭包机制,依赖数组(如
useEffect
的第二个参数)控制副作用触发时机。
性能优化
- 依赖数组优化:合理设置依赖数组,避免不必要的副作用执行。
useCallback
与useMemo
:缓存函数或计算结果,避免子组件因引用变化重新渲染。React.memo
配合 Hooks:减少组件重复渲染。
常见问题与解决方案
- 无限循环:因依赖数组设置不当导致
useEffect
反复触发。 - 状态延迟更新:批量更新机制下,连续调用
setState
可能合并为一次更新。 useRef
保存变量:解决闭包陷阱(如定时器中访问最新状态)。
设计模式
- 状态逻辑复用:通过自定义 Hook 封装逻辑(如
useFetch
数据请求)。 - 复杂状态管理:
useReducer
适合多状态关联的场景,可替代部分 Redux 需求。 - 上下文共享:
useContext
+useReducer
实现轻量级状态管理。
代码示例
useState
基础用法
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Clicked {count} times</button>;
useEffect
清理副作用
useEffect(() => {const timer = setInterval(() => console.log('Tick'), 1000);return () => clearInterval(timer); // 清理
}, []);
自定义 Hook 示例
function useToggle(initialValue = false) {const [value, setValue] = useState(initialValue);const toggle = () => setValue(!value);return [value, toggle];
}
面试高频问题
- 为什么 Hooks 不能放在条件或循环中调用?
useEffect
和useLayoutEffect
的区别?- 如何用 Hooks 模拟
componentDidMount
? - 如何解决闭包导致的 stale state 问题?
- 自定义 Hook 的设计原则是什么?
Hooks 不能放在条件或循环中调用的原因
React Hooks 的调用顺序必须保持一致,这是 React 内部依赖调用顺序管理状态的核心机制。条件或循环会导致 Hooks 的调用顺序在不同渲染中发生变化,从而破坏状态的一致性,引发难以追踪的 bug。
Hooks 的规则通过 ESLint 插件 eslint-plugin-react-hooks
强制执行,确保开发者遵循这一原则。违反规则会导致 React 抛出错误,提示 Hooks 的调用数量不一致。
useEffect 和 useLayoutEffect 的区别
useEffect
是异步执行的,在浏览器完成渲染后才触发副作用,适合处理数据订阅、手动 DOM 操作等非紧急任务。
useLayoutEffect
是同步执行的,在 DOM 更新后但浏览器绘制前触发,适合需要同步读取布局或避免视觉闪烁的场景。滥用可能导致性能问题。
两者的函数签名相同,区别仅在于执行时机。通常优先使用 useEffect
,仅在布局相关需求时选择 useLayoutEffect
。
用 Hooks 模拟 componentDidMount
将 useEffect
的依赖数组设为空,确保副作用仅在组件挂载时执行一次:
useEffect(() => {// 此处代码仅在组件挂载时运行
}, []); // 空依赖数组
注意:这与 componentDidMount
的语义并不完全相同,例如在 SSR 场景下仍需额外处理。
解决闭包导致的 stale state 问题
闭包问题通常出现在异步操作中访问旧状态值。解决方法包括:
- 使用函数式更新:确保获取最新状态
setCount(prevCount => prevCount + 1);
- 通过 ref 保存最新值:配合
useEffect
同步状态
const countRef = useRef(count);
useEffect(() => {countRef.current = count;
}, [count]);
- 合理设置依赖数组:确保回调重新创建时捕获最新值
自定义 Hook 的设计原则
- 单一职责:每个 Hook 应解决一个特定问题,避免功能混杂
- 命名清晰:使用
use
前缀明确标识为 Hook - 状态隔离:不同组件使用同一 Hook 时状态独立
- 组合优先:通过组合基础 Hooks 实现复杂逻辑
- 文档完备:明确输入输出和使用约束
- 性能优化:合理使用
useMemo
/useCallback
避免无效计算
自定义 Hook 本质上是对状态逻辑的复用,设计时应遵循 React Hooks 的既有规则。