React Hooks 面试题全解析
从源码原理到实战技巧,助您斩获大厂 Offer
一、React Hooks 基础概念与演进
在 React 16.8 版本之前,函数组件(Functional Components)仅作为无状态组件使用,无法管理本地状态(State)或处理生命周期事件。随着 React Hooks 的引入,这一限制被打破。Hooks 允许开发者在不编写 Class 组件的情况下,使用 State 和其他 React 特性。这一变革不仅简化了代码结构,还极大地提升了代码的可读性和复用性。
1.1 为什么需要 Hooks?
- 解决复杂组件难以维护的问题: Class 组件中,生命周期方法(如 componentDidMount, componentDidUpdate, componentWillUnmount)往往被拆分在多个方法中,导致逻辑分散。Hooks 允许将相关联的逻辑捆绑在一起。
- 复用状态逻辑: 在 Hooks 出现之前,复用状态逻辑主要依赖高阶组件(HOC)或 Render Props,这会导致组件树嵌套过深(俗称“回调地狱”的变体)。Hooks 提供了更直接的复用方式。
- 简化组件逻辑: 函数组件更加轻量,易于理解和测试,没有 this 指向的困扰。
难以理解的 this 指向,生命周期方法分散,逻辑复用困难,测试复杂。
逻辑内聚,易于复用(自定义 Hooks),函数式编程,类型推断友好。
二、核心 Hooks API 深度解析
在面试中,面试官通常会针对 useState、useEffect 和 useRef 进行深度提问。以下是对这三个核心 API 的详细解析及常见面试题解答。
2.1 useState:状态管理的基石
useState 是最基础的 Hook,用于在函数组件中添加状态。它返回一个状态值和一个更新该状态的函数。
2.2 useEffect:副作用处理的艺术
useEffect 用于处理副作用,如数据获取、订阅或手动修改 DOM。它相当于 componentDidMount, componentDidUpdate, 和 componentWillUnmount 的组合。
基本数据获取
依赖数组的作用
依赖数组决定了 useEffect 在何时执行:
- 无依赖数组: 每次渲染后都执行。
- 空数组 []: 仅在组件挂载和卸载时执行。
- [prop, state]: 当依赖项发生变化时执行。
清理函数的重要性
返回的函数会在组件卸载前执行,用于清理副作用,如取消订阅、清除定时器。
2.3 useRef:引用 DOM 与持久化变量
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。它有两个主要用途:
- 访问 DOM 元素: 直接操作 DOM 节点。
- 保存可变值: 类似于 Class 组件中的实例属性。修改 .current 不会触发重新渲染,适合保存不需要触发渲染的历史值或定时器 ID。
三、React 性能优化与 Hooks
在 React 应用中,性能优化是面试中的高频考点。以下两个 Hook 是性能优化的关键工具。
3.1 useMemo vs useCallback
许多开发者容易混淆这两个 API。简单来说:useMemo 用于缓存计算结果,useCallback 用于缓存函数定义。
| 特性 | useMemo | useCallback |
|---|---|---|
| 返回值 | 缓存的值(Memoized value) | 缓存的函数(Memoized function) |
| 主要用途 | 避免昂贵的计算(如列表过滤、排序) | 避免子组件不必要的重渲染(作为 props 传递) |
| 等价于 | Class 组件中的实例变量 + 手动判断 | Class 组件中的 bound 方法 |
| 示例 | const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); |
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); |
3.2 React.memo
虽然 React.memo 不是 Hook,但它常与 Hooks 配合使用。它用于包装组件,防止在 props 未发生变化时进行不必要的重新渲染,类似于 Class 组件中的 shouldComponentUpdate。
四、自定义 Hooks 实战
自定义 Hook 是一个函数,其名称以 "use" 开头,内部可以调用其他 Hooks。它的核心目的是复用状态逻辑,而非复用组件本身。
4.1 场景:防抖搜索
在实现搜索框时,我们通常需要防抖(Debounce)功能。通过自定义 Hook,我们可以优雅地处理这一逻辑。
五、常见陷阱与最佳实践
掌握 Hooks 不仅仅是知道 API 怎么用,更要了解其背后的规则,避免踩坑。
5.1 Hooks 规则
- 只在最顶层调用 Hook: 不要在循环、条件或嵌套函数中调用 Hook。这保证了每次渲染时 Hook 的调用顺序一致,React 才能正确维护内部状态。
- 只在 React 函数组件中调用 Hook: 不能在普通的 JavaScript 函数中调用 Hook。
5.2 闭包陷阱(Stale Closure)
这是面试中的高频难题。当 useEffect 或 useCallback 依赖了一个状态,但在回调中使用了该状态,而依赖数组中未包含它时,就会捕获到过时的状态。
在 setInterval 中读取 state,但依赖数组为空,导致定时器内的 state 始终为初始值。
1. 将 state 加入依赖数组。
2. 使用 useRef 保存最新值。
3. 使用函数式更新 setState(prev => prev + 1)。
5.3 无限渲染循环
如果在 useEffect 中直接调用 setState,且未提供依赖数组,会导致组件无限重新渲染。务必检查依赖数组是否正确,或确保状态更新是有条件的。