React Hooks 面试专题深度解析

从面试角度深度解析 React Hooks,包含原理、心智模型、常见坑和高频问答。

#tech / dev / frame #resource / react #type / synthesis #status / evergreen

[!info] related notes

React Hooks 面试专题深度解析

面试中问到 Hooks 时,不要只背 API,要展示你对「Hook 为什么这么设计」的理解。

一、必答题:Hooks 解决了什么

作为被面试者,首先要能清晰回答 Hooks 出现的背景:

类组件的三个问题

  1. 逻辑分散:同一件事分裂在 componentDidMountcomponentDidUpdatecomponentWillUnmount
  2. 复用困难:想复用逻辑只能靠 HOC、render props,嵌套很深
  3. 函数组件能力弱:函数组件原本不能直接持有 state 和副作用

Hooks 的核心价值

把「和某个功能相关的逻辑」聚合起来,而不是按生命周期切碎。

进一步理解:react-hooks


二、必答题:Hook 的调用顺序机制

这是 Hooks 最重要的原理,面试必问。

React 怎么知道哪个 state 属于哪个 Hook

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('A');
}

答案:React 依赖 Hook 的调用顺序,而不是变量名。

React 内部更接近这样理解:

  • 第 1 个 Hook 槽位:count
  • 第 2 个 Hook 槽位:name

为什么不能写在条件、循环里

function Bad({ visible }) {
  if (visible) {
    useEffect(() => {}, []);  // 错!
  }
  const [count, setCount] = useState(0);
}

visible=true 时,useEffect 是第 1 个 Hook 当 visible=false 时,useState 变成第 1 个 Hook

调用顺序一乱,React 就把状态对错位置。

进一步理解:react-use-state


三、useState 高频面试题

1. 为什么不能直接 count = count + 1

因为组件函数里的变量只是当前这次执行的局部变量。React 不会因为你改了普通变量就重新渲染,也不会把它保存到下一次渲染里。

2. 函数式更新为什么重要

setCount(c => c + 1);  // 推荐
setCount(count + 1);   // 不推荐

函数式更新拿到的是最新状态,不容易被闭包里旧值坑到。

3. 惰性初始化

const [value, setValue] = useState(() => expensiveInit());

只会在初始渲染时计算一次。

进一步理解:react-use-state


四、useEffect 高频面试题

1. useEffect 的本质是什么

必答题:React 官方明确说,useEffect 是「让组件与外部系统同步」。

不是:

  • 「组件渲染完一定要执行的地方」
  • 「代替所有事件处理器的地方」
  • 「处理纯同步逻辑的地方」

2. 依赖数组的三种写法

写法什么时候运行
useEffect(fn)每次渲染后
useEffect(fn, [])只在初次挂载后
useEffect(fn, [count])首次 + count 变化时

3. 为什么开发环境下 Effect 执行两遍

React 官方:在严格模式下,开发环境会额外执行一次 setup + cleanup,这是压力测试,用来帮助你发现 cleanup 是否写对。

这不是 bug,是 Feature。

4. 常见坑:闭包问题

useEffect(() => {
  const id = setInterval(() => {
    console.log(count);  // 永远打印 0
  }, 1000);
  return () => clearInterval(id);
}, []);  // count 没在依赖里!

解决方法:

  • 把依赖写对:useEffect(..., [count])
  • 用 ref 保存最新值

5. 什么时候不用 Effect

  • 派生状态可以直接在渲染时计算
  • 用户事件应该放在事件处理器里

进一步理解:react-use-effect, react-use-layout-effect


五、useRef 高频面试题

useRef 和 useState 的区别

  • state 变了会触发重渲染
  • ref.current 变了不会触发重渲染

useRef 的两个用途

  1. 引用 DOM:inputRef.current.focus()
  2. 保存不触发渲染的可变值:timer id、上一次的值、第三方实例

进一步理解:react-use-ref


六、useMemouseCallback 高频面试题

什么时候该用 useMemo

  • 计算真的昂贵(大数组过滤、复杂排序)
  • 需要「引用稳定」传给 memo 子组件

什么时候不该用

  • 不要把 useMemo 当作「到处加一点更高级」
  • 缓存也有成本:依赖比较、额外心智负担

useCallback 和 useMemo 的关系

const fn = useCallback(callback, deps);
// 等于
const fn = useMemo(() => callback, deps);

进一步理解:react-use-memo, react-use-callback


七、自定义 Hook 面试题

自定义 Hook 是什么

把一段可复用的「状态 + 副作用 + 逻辑组合」提取成函数。

function useDebounce(value, delay) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

它不是共享状态实例

每次调用自定义 Hook,都是独立的一套 Hook 状态槽位。自定义 Hook 共享的是逻辑,不是状态实例。

进一步理解:react-custom-hooks


八、最常见的 9 个坑

错误写法正确写法
条件调用 Hookif (ok) useEffect(...)必须顶层调用
依赖少写useEffect(fn, []) 少写了 userId依赖要反映真实引用
依赖乱写故意少写来「优化」依赖必须完整
Effect 做派生状态useEffect(() => setFiltered(...))直接渲染时计算
Effect 响应用户事件useEffect(() => { if (submitted) ... })放在事件处理器里
把 ref 当 stateref.current++ 期望刷新 UI用 state
把 state 当 ref想存 timer id 但用 state用 ref
过度使用缓存到处 useMemo/useCallback只在必要时用
忘记 cleanup定时器/订阅没清理Effect 必须 return cleanup

九、选择 Hook 的思维框架

面试时被问到「这个场景用什么 Hook」,按这个顺序想:

  1. 需要让 UI 记住某个值?useState / useReducer
  2. 要和外部系统同步?useEffect / useLayoutEffect
  3. 只想保存不触发渲染的值?useRef
  4. 跨层传数据?useContext
  5. 有昂贵计算?useMemo
  6. 需要稳定函数引用?useCallback
  7. 想复用一段逻辑? → 自定义 Hook

十、面试加分回答

加分项 1:提到 React Compiler

React 官方现在也在强调编译器自动记忆化能力,useCallback 文档提到 React Compiler 会自动对值和函数做记忆化,从而减少手动调用 useCallback 的需求。

加分项 2:提到闭包是 JavaScript 特性

闭包不是 React 特有问题,但在 Hooks 里会特别明显,因为组件每次渲染都会形成新闭包。

加分项 3:展示正确的数据流思维

最有效的优化经常不是「缓存更多」,而是「少建模错误的状态」。


十一、面试答题模板

如果被问到某个 Hook,不想答散,可以按这个顺序组织:

  1. 这个 Hook 要解决什么问题
  2. 它和相邻 Hook 的边界是什么
  3. 它最常见的坑是什么
  4. 它在真实项目里通常用在哪

例如被问 useRef,不要只答“拿 DOM”,而要补:

  • 它也能保存不触发渲染的可变值
  • 它和 state 的边界是什么
  • 它为什么常用来解决闭包旧值问题

十二、最值得记住的 10 句话

  1. Hooks 的本质是让函数组件拥有状态和副作用能力
  2. React 依赖 Hook 的调用顺序来匹配状态槽位
  3. 所以 Hook 只能在顶层调用,不能写进条件和循环
  4. useState 用来驱动 UI
  5. useRef 用来保存不触发渲染的可变值
  6. useEffect 是「与外部系统同步」,不是「渲染后随便写逻辑」
  7. 依赖数组必须反映真实依赖,不要靠省略依赖来「优化」
  8. 闭包不是 React 特有问题,但在 Hooks 里会特别明显
  9. useMemo 缓存值,useCallback 缓存函数引用
  10. 自定义 Hook 复用的是逻辑,不是共享同一份状态
创建于 2026/3/24 更新于 2026/5/27