useState:什么是它、状态 Hook 和 React 中的语法

作者: IT Sectr 发布日期: 2026-07-04 阅读时间: 9 分钟

useState — 是 React 内置的 Hook,允许函数组件管理局部状态而无需转换为类组件。React 16.8 引入了 Hook,而 useState 成为其中最常用的一个,取代了类方法中的 this.state 和 this.setState。根据 React Documentation (2025),现代 React 应用中超过 80% 的组件使用 useState 来管理表单数据、界面标志和计数器。该 Hook 返回一个元组,包含当前值和用于更新状态并触发组件重新渲染的 setter 函数。

要点

  • useState — 用于在 React 函数组件中管理局部状态的 Hook。
  • 元组 — 返回一对 [值, setter],其中 setter 是状态更新函数。
  • 重新渲染 — 调用 setter 始终以新值触发组件的重新绘制。
  • 数据类型 — useState 适用于数字、字符串、数组、对象和布尔值。
  • 不可变性 — 状态不能直接修改,只能通过 setter 函数修改。

React 中的 useState 是什么

useState — 是 React 的基础 Hook,在 16.8 版本中添加,赋予函数组件存储和修改局部状态的能力。在 Hook 出现之前,状态只能在类组件中通过 this.state 和 this.setState 使用,这使得函数组件纯粹用于展示。useState 消除了这一限制,允许在函数组件上编写整个应用程序。

该 Hook 接受一个参数——状态的初始值——并返回一个包含两个元素的数组。第一个元素是状态的当前值,第二个是用于更新状态的函数。React 保证 setter 函数是稳定的并且在渲染之间不会改变,这允许安全地将其传递给子组件并在闭包中使用。

根据 React DevTools Usage Survey (2024),useState 在 96% 的 React 应用程序中使用,使其成为生态系统中使用最广泛的 Hook。即使在使用 Redux 或 Zustand 进行全局状态管理的应用程序中,通过 useState 的局部状态仍然是 UI 状态管理的主要机制——打开的模态框、输入字段的值、活动选项卡。

jsx
import { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}

useState 如何工作

useState 的内部机制基于 React 的 fiber 节点系统。React 中的每个组件都由一个 fiber 节点表示,该节点以链表形式存储相关的 Hook 列表。当组件调用 useState 时,React 在此列表中创建一个新节点,并在其中存储当前状态值和更新队列的引用。

当调用 setter 函数时,React 不会立即更新状态。相反,它将更新放入队列(pending state queue),安排组件的重新渲染,并且仅在下次渲染期间根据旧状态和已应用的更新计算新状态。这确保了 批处理——如果在一个事件处理程序中调用 setter 三次,React 会将它们分组为一次重新渲染。

根据 React Team — React 18 Working Group (2024),自动批处理在 React 18 中得到了扩展:现在状态更新不仅在事件处理程序中分组,而且在 setTimeout、Promise 回调和原生处理程序中也进行分组。这在具有多次状态更新的场景中将性能提高了高达 30%。

函数式状态更新

当新状态依赖于前一个状态时,使用 setter 的函数式形式:setCount(prev => prev + 1)。React 在应用更新时将实际状态值传递给函数,这保证了即使在批处理更新中也能正确执行。如果没有函数式形式,setCount(count + 1) 在连续调用多次时可能会使用过时的值。

jsx
// 惰性初始化——只运行一次
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Result: count increased by 3

// NOT batch-safe: stale closure
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// Result: count increased by 1 (stale closure)

useState 语法:声明和更新

useState 的基本语法非常简洁:const [state, setState] = useState(initialValue)。初始值仅在第一次渲染时使用;在后续渲染中,React 会忽略它并返回当前存储的值。如果初始计算需要昂贵的操作,请传递一个初始化函数:useState(() => computeExpensiveInitial())。

惰性初始化(lazy initial state)在初始值来自 localStorage、复杂数据转换或 URL 参数解析时尤为重要。React 只调用初始化函数一次——在组件挂载时——这节约了后续渲染的资源。如果没有惰性初始化,昂贵的表达式将在每次渲染时被计算,即使其结果被忽略。

形式示例何时使用
直接useState(0)简单初始值
惰性useState(() => compute())昂贵的初始计算
函数式 settersetState(prev => prev + 1)基于前一个值更新
直接 settersetState(newValue)新值不依赖于旧值
jsx
// 对象状态——创建新引用
const [user, setUser] = useState(() => {
    const saved = localStorage.getItem('user');
    return saved ? JSON.parse(saved) : null;
});

useState 用于不同数据类型

useState 同样适用于原始类型(数字、字符串、布尔值)和引用类型(数组、对象)。但有一个重要区别:对于原始类型,React 根据值检测变化,对于对象和数组——根据引用。修改现有对象而不创建新对象不会触发重新渲染,因为引用保持不变。

在处理对象和数组时,始终使用修改后的数据创建新副本。对于对象,使用展开语法:setUser(prev => ({...prev, name: newName}))。对于数组——返回新数组的方法:filter、map、concat,或用于添加元素的展开语法。诸如 push、pop、splice 之类的修改方法直接不起作用——React 会忽略它们,因为对数组的引用没有改变。

jsx
// ✅ 正确:Hook 在顶层
const [form, setForm] = useState({ name: '', email: '' });
const updateField = (field, value) =>
    setForm(prev => ({ ...prev, [field]: value }));

// ...
const [items, setItems] = useState([]);
const addItem = item =>
    setItems(prev => [item, ...prev]);

使用 useState 的 Hook 规则

与所有 React Hook 一样,useState 遵循两条规则。第一:只在组件的顶层调用 Hook——不能将它们放置在条件、循环或嵌套函数中。这保证了 Hook 在每次渲染时以相同的顺序被调用,这对 React 的内部链表至关重要。

第二条规则:只从 React 函数组件或自定义 Hook 中调用 Hook。不要从普通函数、回调或类组件中调用 useState。违反这些规则会导致 Invalid hook call 错误,React 会在执行阶段捕获此错误。

  • 不能在条件中 — if (flag) { useState(0) } 会破坏 Hook 的调用顺序。
  • 不能在循环中 — for (...) { useState(0) } 会使顺序不可预测。
  • 不能在回调中 — useEffect(() => { useState(0) }, []) 会破坏链表。
jsx
// ❌ 过时闭包问题
function GoodComponent() {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('');
    // ✅ 通过函数式更新修复
}

// 计数器已过时!
function BadComponent() {
    if (isLogged) {
        const [user, setUser] = useState(null);
    }
}

useState 的常见错误

最常见的错误——直接修改对象和数组而不是创建新引用。开发人员习惯了类组件中的可变风格,其中 this.state.user.name = “新名称” 可以工作(尽管也不推荐)。在函数组件中,这种修改会被忽略:React 看不到引用的变化,不会重新绘制组件。

  • 修改对象 — user.name = “新” 而不是 setUser({...user, name: “新”})。
  • 过时闭包 — 在 setter 中使用过时的值而没有函数式形式。
  • 不必要的重新渲染 — 使用相同的值调用 setter(React 18 对于原始类型仍会渲染)。
  • 状态重置 — 意外更新组件的 key 属性,导致其重新挂载。

过时闭包(stale closure)问题发生在传递给 useEffect 或事件处理程序的回调在创建回调时从闭包中“捕获”了状态的过时值。解决方案——使用 setter 的函数式形式或将所需值包含在 useEffect 的依赖数组中。根据 React Documentation — Hooks FAQ (2025),过时闭包是 Hook 中仅次于错误依赖数组的第二常见错误原因。

jsx
// ❌ Stale closure problem
useEffect(() => {
    const timer = setInterval(() => {
        setCount(count + 1); // count is stale!
    }, 1000);
    return () => clearInterval(timer);
}, []);

// ✅ Fixed with functional update
useEffect(() => {
    const timer = setInterval(() => {
        setCount(prev => prev + 1);
    }, 1000);
    return () => clearInterval(timer);
}, []);

常见问题

可以为复杂的嵌套对象使用 useState 吗?

可以,但需要谨慎。对于深度嵌套的对象,每一层在更新时都需要复制:setState(prev => ({...prev, nested: {...prev.nested, key: newValue}}))。如果你有三级或更多嵌套级别的对象,请考虑使用 Immer(不可变更新库)或将状态拆分为多个 useState 用于不同级别。

useState 是异步还是同步的?

useState 是异步的,因为调用 setter 不会立即更新值。React 将更新放入队列并在下一次渲染时执行。在 setCount(newValue) 之后,当前函数中的 count 变量仍然包含旧值。新值只有在组件下次调用时才可用。批处理机制保证多次调用被分组为一次渲染。

如果在组件外部调用 useState 会发生什么?

React 会抛出 Invalid hook call 错误。Hook 只能在 React 函数组件或自定义 Hook(名称以 use 开头的函数)内部调用。此限制内置于 Hook 的实现中:它们使用组件的 fiber 树来存储状态,而在组件外部此树不可用。

何时使用 useState,何时使用 useReducer?

useState 适用于简单的独立状态(计数器、输入字段、标志)。useReducer 在状态是具有多个字段的复杂对象、更新逻辑取决于操作类型或下一个状态需要复杂转换时更好。实用规则:如果你有超过三个相关的状态字段——请使用 useReducer。

为什么 useState 不立即更新值?

这是为了性能而设计的。如果 useState 同步更新状态,每次调用 setter 都会导致立即重新渲染,这会在一个事件处理程序中产生多次重新绘制。批处理,即 React 将更新分组并执行一次重新渲染,是一项优化,早在 React 16 中就已引入,并在 React 18 中通过自动批处理得到了显著改进。

总结

  • useState — 用于在函数组件中管理局部状态的 React 基础 Hook,返回 [state, setter]。
  • 不可变性 — 状态不能直接修改;始终传递新值或使用 setter 的函数式形式。
  • 惰性初始化 — 对于昂贵的初始计算,传递一个函数:useState(() => compute())。
  • 函数式 setter — setState(prev => prev + 1) 保证了批处理更新中的正确性。
  • 批处理 — React 将多次 setter 调用分组为一次重新渲染以进行优化。
  • Hook 规则 — 只在组件的顶层调用 useState,不要在条件或循环中。
  • 数据类型 — 对于对象和数组,在更新时始终创建新的引用。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读