```html
欢迎来到 React Hooks 面试备考中心 | 更新时间:2023-10-27

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 指向的困扰。
Class 组件痛点

难以理解的 this 指向,生命周期方法分散,逻辑复用困难,测试复杂。

Hooks 优势

逻辑内聚,易于复用(自定义 Hooks),函数式编程,类型推断友好。

二、核心 Hooks API 深度解析

在面试中,面试官通常会针对 useState、useEffect 和 useRef 进行深度提问。以下是对这三个核心 API 的详细解析及常见面试题解答。

2.1 useState:状态管理的基石

useState 是最基础的 Hook,用于在函数组件中添加状态。它返回一个状态值和一个更新该状态的函数。

const [count, setCount] = useState(0); // 面试题:useState 的初始状态是每次渲染都计算,还是只计算一次? // 答:只计算一次。如果初始状态依赖于之前的状态,应使用函数式更新。 const [count, setCount] = useState(() => { // 这里的计算只在组件挂载时执行一次 return initialCount; });

2.2 useEffect:副作用处理的艺术

useEffect 用于处理副作用,如数据获取、订阅或手动修改 DOM。它相当于 componentDidMount, componentDidUpdate, 和 componentWillUnmount 的组合。

基本数据获取

useEffect(() => { const fetchData = async () => { const result = await axios.get('/api/data'); setData(result.data); }; fetchData(); }, []); // 模拟 componentDidMount

依赖数组的作用

依赖数组决定了 useEffect 在何时执行:

  • 无依赖数组: 每次渲染后都执行。
  • 空数组 []: 仅在组件挂载和卸载时执行。
  • [prop, state]: 当依赖项发生变化时执行。

清理函数的重要性

返回的函数会在组件卸载前执行,用于清理副作用,如取消订阅、清除定时器。

useEffect(() => { const timer = setInterval(() => { console.log('Tick'); }, 1000); return () => { clearInterval(timer); // 清理定时器,防止内存泄漏 }; }, []);

2.3 useRef:引用 DOM 与持久化变量

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。它有两个主要用途:

  1. 访问 DOM 元素: 直接操作 DOM 节点。
  2. 保存可变值: 类似于 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,我们可以优雅地处理这一逻辑。

import { useState, useEffect } from 'react'; function useDebounceValue(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // 使用示例 function SearchBox() { const [input, setInput] = useState(''); const debouncedInput = useDebounceValue(input, 500); useEffect(() => { // 使用 debouncedInput 进行搜索请求 console.log('Searching for:', debouncedInput); }, [debouncedInput]); return setInput(e.target.value)} />; }

五、常见陷阱与最佳实践

掌握 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,且未提供依赖数组,会导致组件无限重新渲染。务必检查依赖数组是否正确,或确保状态更新是有条件的。

```
斌知网
蜀ICP备2026017620号-5