软件开发札记 · 持续修订

菜单
React

React Hooks 深入理解

从状态快照、闭包与副作用同步的角度,系统理解 React Hooks 的工作方式。

2026年7月30日4 分钟阅读618

React Hooks 深入理解

React Hooks 让函数组件拥有状态、上下文和副作用能力。真正掌握 Hooks 的关键,不是记忆 API,而是理解一次渲染就是一次独立的状态快照。

React Hooks 介绍

每次组件渲染时,React 都会重新调用组件函数。当前渲染中的变量和事件处理函数会捕获这一轮渲染的状态。Hooks 必须始终在组件顶层调用,React 才能通过稳定的调用顺序关联每一个状态槽位。

Hook主要用途常见误区
useState保存影响界面的状态直接修改对象后传回原引用
useEffect与外部系统同步把派生数据放进 Effect
useMemo缓存昂贵计算为所有简单计算添加缓存
自定义 Hook复用有状态逻辑误以为可以共享同一份状态

useState

状态更新会请求一次新的渲染,而不会修改当前渲染已经捕获的值。当新状态依赖旧状态时,应使用函数式更新。

Counter.tsx
import { useState } from "react";
 
export function Counter() {
  const [count, setCount] = useState(0);
 
  function incrementThreeTimes() {
    setCount((current) => current + 1);
    setCount((current) => current + 1);
    setCount((current) => current + 1);
  }
 
  return (
    <button onClick={incrementThreeTimes}>
      当前计数:{count}
    </button>
  );
}

对象状态

对象和数组状态应该通过新值替换。展开语法适合浅层更新;复杂嵌套状态更适合重新设计数据结构或使用 reducer。

ProfileForm.tsx
setProfile((profile) => ({
  ...profile,
  name: nextName,
}));

useEffect

Effect 用来把 React 状态与浏览器 API、网络连接或第三方组件等外部系统保持同步。依赖数组描述 Effect 使用了哪些响应式值。

OnlineStatus.tsx
import { useEffect, useState } from "react";
 
export function OnlineStatus() {
  const [online, setOnline] = useState(true);
 
  useEffect(() => {
    const update = () => setOnline(navigator.onLine);
    update();
    window.addEventListener("online", update);
    window.addEventListener("offline", update);
 
    return () => {
      window.removeEventListener("online", update);
      window.removeEventListener("offline", update);
    };
  }, []);
 
  return <span>{online ? "在线" : "离线"}</span>;
}

如果一个值可以在渲染期间根据 props 和 state 计算出来,通常不需要 Effect。

useMemo

useMemo 是性能优化手段,不是语义保证。只有当计算本身昂贵,或者稳定引用能避免下游组件重复工作时才使用它。

ProductList.tsx
const visibleProducts = useMemo(
  () => products.filter((product) => product.name.includes(query)),
  [products, query],
);

判断是否值得缓存,可以先用 React DevTools Profiler 找到真正的性能热点,再做针对性优化。

自定义 Hooks

自定义 Hook 把状态逻辑抽取为可组合的函数。每次调用都会获得独立状态,只复用逻辑,不共享状态实例。

useDocumentTitle.ts
import { useEffect } from "react";
 
export function useDocumentTitle(title: string) {
  useEffect(() => {
    const previousTitle = document.title;
    document.title = title;
    return () => {
      document.title = previousTitle;
    };
  }, [title]);
}

下面是组件渲染与 Effect 执行的基本顺序:

flowchart LR
  A[状态更新] --> B[组件重新渲染]
  B --> C[React 提交 DOM]
  C --> D[浏览器绘制]
  D --> E[运行 Effect]
  E -->|依赖变化| F[先执行上次清理函数]
  F --> E

小结

  • 把每次渲染理解为一张不可变的状态快照。
  • 使用函数式更新处理依赖旧状态的连续更新。
  • Effect 只负责与外部系统同步。
  • useMemo 应由真实性能问题驱动。
  • 自定义 Hook 用于复用状态逻辑,而不是共享状态。