软件开发札记 · 持续修订

菜单
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 用于复用状态逻辑,而不是共享状态。