简单易懂的 React useState() Hook 指南(长文建议收藏)

本文详细介绍了React中的useState Hook,包括如何创建和更新状态、处理多个状态、延迟初始化以及常见陷阱。同时,对比了useState与useReducer在复杂状态管理上的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:Dmitri Pavlutin
译者:前端小智
来源:dmitripavlutin.com


腾讯云最近在做活动,百款云产品低至 1 折,可以点击 阅读原文 进行参与


状态是隐藏在组件中的信息,组件可以在父组件不知道的情况下修改其状态。我更偏爱函数组件,因为它们足够简单,要使函数组件具有状态管理,可以useState() Hook。

本文会逐步讲解如何使用useState() Hook。此外,还会介绍一些常见useState() 坑。

1.使用 `useState()` 进行状态管理

无状态的函数组件没有状态,如下所示(部分代码):

可以找 codesandbox 尝试一下。

运行效果:

这时,要如何添加一个按钮来打开/关闭灯泡呢?为此,咱们需要具有状态的函数组件,也就是有状态函数组件。

useState()是实现灯泡开关状态的 Hoook,将状态添加到函数组件需要4个步骤:启用状态、初始化、读取和更新。

1.1 启用状态

要将<Bulbs> 转换为有状态组件,需要告诉 React:从'react'包中导入useState钩子,然后在组件函数的顶部调用useState()

大致如下所示:

Bulbs函数的第一行调用useState()(暂时不要考Hook的参数和返回值)。重要的是,在组件内部调用 Hook 会使该函数成为有状态的函数组件。

启用状态后,下一步是初始化它。

1.2初始化状态

始时,灯泡关闭,对应到状态应使用false初始化 Hook:

useState(false)false初始化状态。

启用和初始化状态之后,如何读取它?来看看useState(false)返回什么。

1.3 读取状态

当 hook useState(initialState)被调用时,它返回一个数组,该数组的第一项是状态值

const stateArray = useState(false);
stateArray[0]; // => 状态值

咱们读取组件的状态

function Bulbs() {
  const stateArray = useState(false);
  return <div className={stateArray[0] ? 'bulb-on' : 'bulb-off'} />;
}

<Bulbs>组件状态初始化为false,可以打开 codesandbox 看看效果。

useState(false)返回一个数组,第一项包含状态值,该值当前为false(因为状态已用false初始化)。

咱们可以使用数组解构来将状态值提取到变量on上:

import React, { useState } from 'react';

function Bulbs() {
  const [on] = useState(false);
  return <div className={on ? 'bulb-on' : 'bulb-off'} />;
}

on状态变量保存状态值。

状态已经启用并初始化,现在可以读取它了。但是如何更新呢?再来看看useState(initialState)返回什么。

1.4 更新状态

用值更新状态

咱们已经知道,useState(initialState)返回一个数组,其中第一项是状态值,第二项是一个更新状态的函数。

const [state, setState] = useState(initialState);

// 将状态更改为 'newState' 并触发重新渲染
setState(newState);

// 重新渲染`state`后的值为`newState`

要更新组件的状态,请使用新状态调用更新器函数setState(newState)。组件重新渲染后,状态接收新值newState

当点击开灯按钮时将灯泡开关状态更新为true,点击关灯时更新为 false

打开 codesandbox 自行尝试一下。

单击开灯按钮时,lightOn()函数将on更新为true: setOn(true)。单击关灯时也会发生相同的情况,只是状态更新为false

状态一旦改变,React 就会重新渲染组件,on变量获取新的状态值。

状态更新作为对提供一些新信息的事件的响应。这些事件包括按钮单击、HTTP 请求完成等,确保在事件回调或其他 Hook 回调中调用状态更新函数。

使用回调更新状态

当使用前一个状态计算新状态时,可以使用回调更新该状态:

const [state, setState] = useState(initialState);
...
setState(prevState => nextState);

...

下面是一些事例:

接着,通过这种方式重新实现上面电灯的示例:

打开 codesandbox 自行尝试一下。

setOn(on => !on)使用函数更新状态。

1.5 小结一波
  • 调用useState() Hook 来启用函数组件中的状态。

  • useState(initialValue)的第一个参数initialValue是状态的初始值。

  • [state, setState] = useState(initialValue)返回一个包含2个元素的数组:状态值和状态更新函数。

  • 使用新值调用状态更新器函数setState(newState)更新状态。或者,可以使用一个回调setState(prev => next)来调用状态更新器,该回调将返回基于先前状态的新状态。

  • 调用状态更新器后,React 确保重新渲染组件,以使新状态变为当前状态。

2. 多种状态

通过多次调用useState(),一个函数组件可以拥有多个状态。

function MyComponent() {
  const [state1, setState1] = useState(initial1);
  const [state2, setState2] = useState(initial2);
  const [state3, setState3] = useState(initial3);
  // ...
}

需要注意的,要确保对useState()的多次调用在渲染之间始终保持相同的顺序(后面会讲)。

我们添加一个按钮添加灯泡,并添加一个新状态来保存灯泡数量,单击该按钮时,将添加一个新灯泡。

新的状态count 包含灯泡的数量,初始值为1

打开演示,然后单击添加灯泡按钮:灯泡数量增加,单击开/关按钮可打开/关闭灯泡。

  • [on, setOn] = useState(false) 管理开/关状态

  • [count, setCount] = useState(1)管理灯泡数量。

多个状态可以在一个组件中正确工作。

3.状态的延迟初始化

每当 React 重新渲染组件时,都会执行useState(initialState)。如果初始状态是原始值(数字,布尔值等),则不会有性能问题。

当初始状态需要昂贵的性能方面的操作时,可以通过为useState(computeInitialState)提供一个函数来使用状态的延迟初始化,如下所示:

getInitialState()仅在初始渲染时执行一次,以获得初始状态。在以后的组件渲染中,不会再调用getInitialState(),从而跳过昂贵的操作。

4. useState() 中的坑

现在咱们基本已经初步掌握了如何使用useState(),尽管如此,咱们必须注意在使用useState()时可能遇到的常见问题。

4.1 在哪里调用 `useState()`

在使用useState() Hook 时,必须遵循 Hook 的规则

  1. 仅顶层调用 Hook :不能在循环,条件,嵌套函数等中调用useState()。在多个useState()调用中,渲染之间的调用顺序必须相同。

  2. 仅从React 函数调用 Hook:必须仅在函数组件或自定义钩子内部调用useState()

来看看useState()的正确用法和错误用法的例子。

有效调用useState()

useState()在函数组件的顶层被正确调用

function Bulbs() {
  // Good
  const [on, setOn] = useState(false);
  // ...
}

以相同的顺序正确地调用多个useState()调用:

function Bulbs() {
  // Good
  const [on, setOn] = useState(false);
  const [count, setCount] = useState(1);
  // ...

useState()在自定义钩子的顶层被正确调用

function toggleHook(initial) {
  // Good
  const [on, setOn] = useState(initial);
  return [on, () => setOn(!on)];
}

function Bulbs() {
  const [on, toggle] = toggleHook(false);
  // ...
}

useState() 的无效调用

在条件中调用useState()是不正确的:

function Switch({ isSwitchEnabled }) {
  if (isSwitchEnabled) {
    // Bad
    const [on, setOn] = useState(false);
  }
  // ...
}

在嵌套函数中调用useState()也是不对的

4.2 过时状态

闭包是一个从外部作用域捕获变量的函数。

闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。

来看看一个过时的状态是如何表现出来的。组件<DelayedCount>延迟3秒计数按钮点击的次数。

打开演示,快速多次点击按钮。count 变量不能正确记录实际点击次数,有些点击被吃掉。

delay() 是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。

为了解决这个问题,使用函数方法来更新count状态:

现在etCount(count => count + 1)delay()中正确更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。

打开演示,快速单击按钮。延迟过去后,count 能正确表示点击次数。

4.3 复杂状态管理

useState()用于管理简单状态。对于复杂的状态管理,可以使用useReducer() hook。它为需要多个状态操作的状态提供了更好的支持。

假设需要编写一个最喜欢的电影列表。用户可以添加电影,也可以删除已有的电影,实现方式大致如下:

尝试演示:添加和删除自己喜欢的电影。

状态列表需要几个操作:添加和删除电影,状态管理细节使组件混乱。

更好的解决方案是将复杂的状态管理提取到reducer中:

reducer管理电影的状态,有两种操作类型:

  • "add"将新电影插入列表

  • "remove"从列表中按索引删除电影

尝试演示并注意组件功能没有改变。但是这个版本的<FavoriteMovies>更容易理解,因为状态管理已经被提取到reducer中。

还有一个好处:可以将reducer 提取到一个单独的模块中,并在其他组件中重用它。另外,即使没有组件,也可以对reducer 进行单元测试。

这就是关注点分离的威力:组件渲染UI并响应事件,而reducer 执行状态操作。

4.4 状态 vs 引用

考虑这样一个场景:咱们想要计算组件渲染的次数。

一种简单的实现方法是初始化countRender状态,并在每次渲染时更新它(使用useEffect() hook)

useEffect()在每次渲染后调用afterRender()回调。但是一旦countRender状态更新,组件就会重新渲染。这将触发另一个状态更新和另一个重新渲染,依此类推。

可变引用useRef()保存可变数据,这些数据在更改时不会触发重新渲染,使用可变的引用改造一下<CountMyRenders>

打开演示并单击几次按钮来触发重新渲染。

每次渲染组件时,countRenderRef可变引用的值都会使countRenderRef.current ++递增。重要的是,更改不会触发组件重新渲染。

5. 总结

要使函数组件有状态,请在组件的函数体中调用useState()

useState(initialState)的第一个参数是初始状态。返回的数组有两项:当前状态和状态更新函数。

const [state, setState] = useState(initialState);

使用 setState(newState)来更新状态值。另外,如果需要根据先前的状态更新状态,可以使用回调函数setState(prevState => newState)

在单个组件中可以有多个状态:调用多次useState()

当初始状态开销很大时,延迟初始化很方便。使用计算初始状态的回调调用useState(computeInitialState),并且此回调仅在初始渲染时执行一次。

必须确保使用useState()遵循 Hook 规则。

当闭包捕获过时的状态变量时,就会出现过时状态的问题。可以通过使用一个回调来更新状态来解决这个问题,这个回调会根据先前的状态来计算新的状态。

最后,您将使用useState()来管理一个简单的状态。为了处理更复杂的状态,一个更好的的选择是使用useReducer() hook。


原文:https://dmitripavlutin.com/react-usestate-hook-guide/

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

原创系列推荐

1. JavaScript 重温系列(22篇全)

2. ECMAScript 重温系列(10篇全)

3. JavaScript设计模式 重温系列(9篇全)

4. 正则 / 框架 / 算法等 重温系列(16篇全)

5. Webpack4 入门(上)|| Webpack4 入门(下)

6. MobX 入门(上) ||  MobX 入门(下)

7. 59篇原创系列汇总

回复“加群”与大佬们一起交流学习~

点这,与大家一起分享本文吧~

<think>好的,我现在需要帮助用户了解React中的useState Hook的功能和用法,并以Markdown格式总结。首先,我得回忆一下useState的基本知识,确保自己理解正确。 useStateReact一个Hook,用于在函数组件中添加状态。之前,类组件使用this.state和setState来管理状态,而函数组件没有状态能力,直到Hooks的出现。useState的作用就是让函数组件也能拥有状态管理。用户可能需要知道基本用法,比如如何声明状态变量,如何更新它们。 接下来,我需要考虑用户可能的应用场景。比如,使用useState管理表单输入、切换按钮状态、计数器等。我应该举一些例子来说明,这样用户更容易理解。 然后,我需要确保覆盖useState键点,比如初始状态的设置(可以是值或函数)、如何更新状态(直接替换还是函数式更新)、以及处理对象或数组时的注意事项。例如,当状态一个对象时,更新时需要展旧的状态,否则会丢失其他属性。 用户可能还会遇到常见问题,比如状态更新不同步的问题。这时候需要解释闭包和异步更新的概念,可能还需要提到使用函数式更新来确保获取最新的状态值。 另外,用户提供的引用中有提到于children和props的定义,比如引用[2]中的MyButtonProps。虽然用户的问题是useState,但或许可以稍微提及在TypeScript中使用useState时如何定义类型,比如使用泛型参数。不过这可能超出当前问题的范围,需要根据用户是否需要来决定是否包含。 我需要检查是否有任何引用内容与useState相关。用户提供的引用中有React组件的例子,比如引用[1]中的类组件使用了state,但用户现在可能想知道函数组件中的useState。引用[4]提到了Proxy,可能与状态管理。因此,主要useState本身的文档和常见用法。 结构方面,用户要求Markdown格式,所以需要分章节,比如功能概述、基本用法、示例、注意事项等。每个部分用标题,代码块用反引号包裹,并指定语言为jsx。 要注意的是,必须使用用户指定的引用格式,比如在引用的地方添加[^1]等。不过用户提供的引用似乎主要于其他主题,比如React组件结构、TypeScript接口、Webpack配置等,可能与useState的直接联不大。因此,可能需要确认是否有必要引用这些内容。例如,引用[2]中的children定义可能在讲解组件props时有用,但useState主要状态管理,所以可能不需要引用这些内容。不过用户的问题中并没有提到需要引用这些内容,所以可能不需要在回答中包含这些引用,除非有直接联。 用户可能还需要知道useState与类组件中的setState的区别,比如useState不会自动合并对象,需要手动展。这也是一个重要的注意点,应该包含在注意事项中。 最后,生成相关问题,比如如何用useState管理复杂对象、useState与useEffect的结合使用、性能优化等。这些问题应该围绕useState的常见使用场景和潜在问题展。 现在,整理这些思路,组织成结构清晰的Markdown内容,确保每个部分都有示例代码,并正确使用LaTeX格式(如果有数学表达式的话,不过在这个问题中可能用不到,但需要遵循用户的系统级指令,比如行内数学表达式用$...$)。不过React内容中可能不太涉及数学公式,所以可能不需要使用LaTeX,但需要遵循格式要求,比如代码块的正确标记。</think># React useState Hook 功能与用法详解 ## 一、功能概述 `useState` 是 React 提供的基础 Hook,**允许在函数组件中添加局部状态**。与传统类组件的 `this.state` 不同,它通过闭包机制实现状态管理,支持更简洁的组件发模式。 ## 二、基本用法 ### 1. 基础语法 ```jsx const [state, setState] = useState(initialState); ``` - `state`:当前状态值 - `setState`:状态更新函数 - `initialState`:初始状态(支持直接值或函数返回值) ### 2. 代码示例 ```jsx import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>点击次数:{count}</p> <button onClick={() => setCount(count + 1)}> 增加 </button> </div> ); } ``` ## 三、核心特性 ### 1. 延迟初始化 ```jsx // 仅首次渲染时执行计算 const [data] = useState(() => heavyComputation()); ``` ### 2. 函数式更新 ```jsx setCount(prevCount => prevCount + 1); // 确保获取最新状态值 ``` ### 3. 对象/数组更新 ```jsx const [user, setUser] = useState({ name: 'Alice', age: 25 }); // 正确方式:保留其他字段 setUser(prev => ({ ...prev, age: 26 })); ``` ## 四、注意事项 1. **异步更新**:状态更新是批量处理的,直接读取变量可能不是最新值 2. **不可变数据**:必须创建新对象/数组进行更新,直接修改原对象无效 3. **性能优化**:复杂状态建议使用 `useReducer` 代替多个 `useState` ## 五、典型应用场景 1. 表单控件状态管理 2. UI 交互状态(如弹窗开关) 3. 动态样式控制 4. 数据加载状态标记 ```jsx // 表单输入示例 function Form() { const [inputValue, setInput] = useState(''); return ( <input value={inputValue} onChange={(e) => setInput(e.target.value)} /> ); } ``` ## 六、与类组件对比 | 特性 | useState Hook | 类组件 state | |--------------|---------------------|---------------| | 声明方式 | 函数内声明 | constructor | | 更新机制 | 直接替换 | 自动合并对象 | | 多状态管理 | 多次调用 useState | 单对象存储 | | 生命周期绑定 | 需配合 useEffect | 内置生命周期 |
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值