useState
useState
声明一个可以直接更新的状态变量。
一般在组件 最顶层 调用 useState
给组件添加状态变量,一般使用数组解构来命名状态变量,如 [something, setSomething]
。
import { useState } from 'react'
function MyComponent() {
const [age, setAge] = useState(28)
const [name, setName] = useState('Taylor')
const [todos, setTodos] = useState(() => createTodos())
// ...
}
useState(initialState)
参数:
initialState
:初始状态,可以是任何类型的值,初始渲染后,该参数将被忽略。
如果传递一个函数作为 initialState
,它将被视为「初始化函数」。
它应该:
- 是 纯函数
- 不带参数
- 返回任意类型的值
React 将在初始化组件时调用初始化函数,并将其返回值存储为初始状态。
这个例子 没有 传递初始化函数,因此 createInitialTodos
函数会在每次渲染时运行,比如当你在输入框中输入时,此时打开控制台,可以看到每次输入都会调用该函数。
const { useState, useRef, useEffect } = React
function createInitialTodos() {
const initialTodos = []
for (let i = 0; i < 10; i++) {
initialTodos.push({
id: i,
text: 'Item ' + (i + 1)
})
}
console.log('render')
return initialTodos
}
export default function TodoList() {
const [todos, setTodos] = useState(createInitialTodos())
const [text, setText] = useState('')
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button
onClick={() => {
setText('')
setTodos([
{
id: todos.length,
text: text
},
...todos
])
}}
>
Add
</button>
<ul>
{todos.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</>
)
}
这个例子传递了初始化函数,因此 createInitialTodos
函数仅在初始化期间运行。当组件重新渲染,例如你在输入框中键入内容时,它不会再次运行。
const { useState, useRef, useEffect } = React
function createInitialTodos() {
const initialTodos = []
for (let i = 0; i < 10; i++) {
initialTodos.push({
id: i,
text: 'Item ' + (i + 1)
})
}
console.log('render')
return initialTodos
}
export default function TodoList() {
const [todos, setTodos] = useState(createInitialTodos)
const [text, setText] = useState('')
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button
onClick={() => {
setText('')
setTodos([
{
id: todos.length,
text: text
},
...todos
])
}}
>
Add
</button>
<ul>
{todos.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</>
)
}
注意
尽管 createInitialTodos()
的结果仅用于初始渲染,但你仍然在每次渲染时调用此函数。如果它创建大数组或执行昂贵的计算,这可能会浪费资源。
所以对于大型数组的创建,一般传入初始化函数。
返回值:
useState
返回一个数组,使用数组解构接收,其中包含两个值:
- 当前状态。在第一次渲染时,它将与传递的初始状态
initialState
相匹配。 set
函数,用于 状态更新 并 触发重新渲染。
注意:
useState
是一个钩子,所以 只能 在组件的顶层或者你自己的钩子中调用它,不能 在循环或条件中调用它。- 在严格模式下,React 会 调用两次初始化函数,其中一次调用的结果将被忽略,以帮助你找到意外的杂质。这是开发专用行为,不会影响生产环境。如果初始化函数是纯函数,就不会影响应用实际行为(因此初始化函数必须是纯函数)。
注意
若循环调用,则可能造成状态管理混乱、组件不一致、难以调试的问题。
setSomething(nextState)
set
函数会 更新状态并触发重新渲染,可以直接传入 next state
,或者传入一个纯函数。set
函数没有返回值。
setSomething()
是一个异步函数,如下所示,handleClick1
点击一次后,年龄将变为 43
岁,而不是 45
岁。
这是因为调用 set
函数 不会同步 更新已运行代码中的年龄状态变量。
因此,每次调用 setAge(age + 1)
都会变成 setAge(43)
。
function handleClick1() {
setAge(age + 1) // setAge(42 + 1)
setAge(age + 1) // setAge(42 + 1)
setAge(age + 1) // setAge(42 + 1)
}
如果传递一个函数作为 nextState
,它将被视为一个更新函数。它必须是「纯函数」,应将待处理状态作为唯一参数,并返回下一个状态。
React 会将更新函数 放入队列,然后重新渲染组件。
在下一次渲染中,React 将通过将所有队列中的更新器 应用上一个状态来计算下一个状态(previous state
to next state
)。
function handleClick2() {
setAge((a) => a + 1) // setAge(42 => 43)
setAge((a) => a + 1) // setAge(43 => 44)
setAge((a) => a + 1) // setAge(44 => 45)
}
上述代码的处理过程如下:
a => a + 1
将接收42
作为待处理状态,并返回43
作为下一状态。a => a + 1
将接收43
作为待处理状态,并返回44
作为下一个状态。a => a + 1
将接收44
作为待处理状态,并返回45
作为下一个状态。
没有其他队列更新,所以 React 会将 45
作为当前状态保存。
提示
按照惯例,通常以状态变量名称的首字母来命名待处理状态参数,如年龄的 a
,也可以使用更加清晰的名称 prevAge
。
注意
set
函数只更新 下一次渲染 的状态变量。如果在调用set
函数后读取状态变量,您仍然会得到调用前屏幕上的旧值。- 如果提供的新值与当前状态相同(由
Object.is
比较确定),React 将 跳过重新渲染组件及其子代。 - React 会批量更新状态。它会 在所有事件处理程序运行并调用其设置函数后更新屏幕。这可以防止在单个事件中多次重新渲染。在极少数情况下,需要强制 React 提前更新屏幕,例如访问 DOM,可以使用
flushSync
。 - 与初始化函数类似,在严格模式下,React 会调用更新器函数 两次,以验证它们是否纯粹。