Table of Contents

1、概述

在我们创建组件时,我们不仅可以通过 props 接收外部传入的数据,还可以通过 useState 来定义和管理组件内部的状态。

预览
index.tsx
1
import { useState } from 'react'
2
3
export default function Counter() {
4
// 定义一个状态 count,初始值为 0
5
const [count, setCount] = useState(0)
6
return (
7
<div className='flex justify-between items-end'>
8
<p>Count: <span className='text-2xl font-bold font-din'>{count}</span></p>
9
<button className='button' onClick={() => setCount(count + 1)}>
10
Increment
11
</button>
12
</div>
13
)
14
}

当我们期望页面上的 UI 元素能够在用户操作或者数据变化时发生改变,我们就可以使用 useState 来定义一个状态

2、useState 基础语法

useState(initialState)

我们通常在函数组件中的开头几行代码中,使用 useState 来定义一个状态。

code.ts
1
import { useState } from 'react';
2
3
function MyComponent() {
4
const [age, setAge] = useState(28);
5
const [name, setName] = useState('Taylor');
6
const [todos, setTodos] = useState(() => createTodos());
7
// ...

useState 接收一个参数,这个参数是状态的初始值。初始值有两种方式,一种是直接传入一个值,另一种是传入一个函数。该函数会在组件初始化时执行,最终的初始值是该函数的返回值。

这里需要特别注意的是:在后续的更新中,组件函数会再次执行,但是 useState 的初始值赋值行为只会发生一次。 useState 初始值传入函数时,该函数也仅会执行一次,后续更新过程不再执行。

useState 返回一个数组,该数组包含两个元素:

  1. 当前状态值 - 第一个元素是当前状态的值
  2. 状态更新函数 - 第二个元素是用于更新状态的函数

我们通常使用数组解构来获取这两个值:

code.ts
1
const [count, setCount] = useState(0);
2
// ^ ^
3
// 状态值 更新函数

约定命名:状态更新函数通常以 set 开头,后面跟上状态变量的名称。

重点: 当我们调用 setXxx 方法去更新状态时,React 会自动重新执行组件函数,并使用最新的状态值来渲染 UI。

我们可以有以下两种方式来更新状态:

  1. 直接赋值更新
  2. 函数式更新

直接赋值更新

最简单的状态更新方式是直接传入新值:

code.ts
1
const [count, setCount] = useState(0);
2
3
// 点击按钮时更新状态
4
const handleClick = () => {
5
setCount(count + 1); // 直接传入新值
6
};

函数式更新

当新状态值需要基于前一个状态计算时,推荐使用函数式更新:

code.ts
1
const [count, setCount] = useState(0);
2
3
// 推荐:函数式更新
4
const handleClick = () => {
5
setCount(prevCount => prevCount + 1);
6
};

函数式更新的优势:

  • 避免了状态更新的竞态条件
  • 确保基于最新的状态值进行计算
  • 在使用 useEffect 等 Hook 时更加安全
INFO

注意,无论采用哪种方式进行更新,如果新值和旧值相同,都不会重新渲染组件。只有当新值和旧值不同时,才会重新渲染组件。

5、状态更新的异步性

在最新的 React 版本中,状态更新是异步的,这意味着调用 setState 后,状态不会立即更新。React 会在合适的时机批量处理状态更新,以提高性能。

code.ts
1
const [count, setCount] = useState(0);
2
3
const handleClick = () => {
4
setCount(count + 1);
5
console.log(count); // 仍然是旧值!
6
};

如果需要在状态更新后执行某些操作,可以使用 useEffect Hook:

code.ts
1
const [count, setCount] = useState(0);
2
3
useEffect(() => {
4
console.log('count 更新了:', count);
5
}, [count]);
INFO

在 React 之前的版本中,由于底层实现的问题,合成事件和生命周期钩子中,状态更新是异步的,但是在原生时间和 setTimeout 等异步钩子中,由于脱离了 React 的控制,状态更新是同步的。

其他注意事项:

1、useState 是一个 Hook,因此你只能在组件的顶层或你自己的 Hook 中调用它。你不能在循环或条件语句中调用它。如果需要,请提取一个新组件并将状态移入其中。

2、在严格模式下,React 会调用你的初始化函数两次,以帮助你发现意外的不纯代码。这仅限于开发环境,不会影响生产环境。如果你的初始化函数是纯函数(理应如此),这应该不会影响其行为。其中一次调用的结果将被忽略。

3、批量更新

当我们多次调用 set 方法去更新状态时,React 会自动将这些更新合并成一个批量更新,以提高性能。如下例子所示,三次调用 setCount 方法,最终只会触发一次组件的重新渲染。

code.ts
1
const [count, setCount] = useState(0);
2
setCount(count + 1);
3
setCount(count + 1);
4
setCount(count + 1);

这也是为什么我们说,状态更新是异步的核心原因

ok,了解了这些基础知识之后,接下来,我们要用大量的示例来演示如何使用 useState

4、实践案例:计数器

具体的使用比较简单,我们直接通过代码来学习如何使用 useState。主要需要从代码里关注的细节是

  • 关注 useState 的使用
  • 结合 props 封装子组件
  • state 作为 props 的参数传入子组件,会影响子组件的更新
  • props 中传入回调函数,可以实现子组件与父组件的通信,此时,子组件通过回调函数将状态更新结果返回给父组件,父组件通过 props 向子组件传入新的状态值
预览
index.tsx
CounterButton.tsx
StepControl.tsx
1
import { useState } from 'react'
2
import { Plus, Minus, RotateCcw } from 'lucide-react'
3
import CounterButton from './CounterButton'
4
import StepControl from './StepControl'
5
6
export default function EnhancedCounter() {
7
const [count, setCount] = useState(0)
8
const [step, setStep] = useState(1)
9
10
const increment = () => {
11
setCount(prevCount => prevCount + step)
12
}
13
14
const decrement = () => {
15
setCount(prevCount => prevCount - step)
16
}
17
18
const reset = () => {
19
setCount(0)
20
setStep(1)
21
}
22
23
return (
24
<div className="flex flex-col gap-4">
25
<div className="text-center">
26
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-2">
27
增强版计数器
28
</h3>
29
<div className="text-4xl font-bold font-din text-blue-600 dark:text-blue-400 mb-4">
30
{count}
31
</div>
32
</div>
33
34
<div className="flex gap-2 justify-center">
35
<StepControl step={step} onStepChange={setStep} />
36
<CounterButton
37
onClick={decrement}
38
icon={<Minus size={16} />}
39
variant="decrease"
40
>减少</CounterButton>
41
42
<CounterButton
43
onClick={increment}
44
icon={<Plus size={16} />}
45
variant="increase"
46
>增加</CounterButton>
47
48
<CounterButton
49
onClick={reset}
50
icon={<RotateCcw size={14} />}
51
variant="reset"
52
>重置</CounterButton>
53
</div>
54
55
<div className="text-xs text-gray-500 dark:text-gray-400 text-center">
56
点击按钮来增加或减少计数,每次变化 {step}
57
</div>
58
</div>
59
)
60
}

5、总结

通常情况下,我们会把变化的 UI 抽象成状态,然后通过改变状态的方式来改变 UI,因此,我们会分成几个步骤来思考

  1. 声明状态
  2. 通过 JSX 建立 UI 与状态之间的关联关系
  3. 通过更改状态,促使函数组件重新执行,以得到新的 UI 结果

useState 是 React 开发的核心 API,学会了该 API 的使用,基本上也就意味着学会了 React,因此,在后续的章节中,我们还需要多通过几个案例来加深对该 API 的理解。

专栏首页
到顶
专栏目录