easycodesniper

blog by chen qiyi

useState

作为一个React Hook,它只能在组件的顶层调用

语法

useState(initialState)

  • initialState:state的初始值。当传递函数作为initialState时,它将被视为初始化函数,它应该是一个纯函数,不应该传入任何参数,并且应该返回一个值来作为初始值。此参数只有在初始化渲染时使用,在之后将被忽略

useState返回一个数组:状态变量 和 状态设置(set)函数

按照惯例,使用解构赋值的方法来命名状态和状态设置函数,例如const [x, setX] = useState(0)

set函数

通过调用set函数来更新状态变量的值,你可以:

  • 传入一个不同的新值,例如: setName('cqy')
  • 基于待定的state来计算新的状态(更新函数),例如: setAge(a => a + 1)

当将函数作为参数传递给set函数时,它被视为更新函数。它必须是纯函数,唯一的参数是待定的state,并应该返回下一个状态

注意事项

set函数仅更新 下一次 渲染的状态变量,如果在调用set函数后立刻读取状态便了,得到的仍然是渲染在屏幕上的旧值

待定的state

下面的例子中,我们希望在点击之后将count增加3,但结果是count只会增加1。
原因就在于,前一个set函数执行完后,后一个set函数读取的仍然是旧的状态,React会批量处理状态更新,在所有事件处理函数运行并调用其set函数后更新屏幕。所以在页面重新渲染前,获取到的仍然是旧值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function Counter() {
const [count, setCount] = useState(0);

function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}

return (
<button onClick={handleClick}>
You pressed me {count} times
</button>
);
}

为了解决这个问题,可以给set函数传递一个更新函数,它可以根据**最新的state**(即待定的state,可以理解为若没有后续改变state的操作,该state将会在下一次渲染时被使用)状态来计算更新下一个state状态

1
2
3
4
5
6
function handleClick() {
setCount(c => c + 1); // setCount(0 => 1)
setCount(c => c + 1); // setCount(1 => 2)
setCount(c => c + 1); // setCount(2 => 3)
}
// c => c + 1 是更新函数,它获取 待定状态 并根据此计算下一个状态

更新状态中的对象和数组

当把对象和数组作为状态变量时,你应该替换它而不应该单纯的改变对象中的某个属性或者数组中的某项

示例代码:

1
2
3
4
5
6
7
8
9
10
11
const [obj, setObj] = useState({
name: 'easycodesniper',
age: 22
})

obj.name = 'xxx' //❌ 错误的修改方法

setObj({ // 正确的修改方法
...obj,
name: 'xxx'
})

避免重复创建初始状态

React只在初次渲染时保存初始状态,后续渲染都会忽略

1
2
3
function App() {
const [ x, setX ] = useState(createInitialX()) //不好的实践
}

尽管createInitialX()的结果仅用于初次渲染,但是每次重新渲染时都要调用此函数,如果它的执行需要昂贵的计算,会导致资源浪费
为了解决这个问题,你应该把函数本身作为初始化函数传递给useState,而不是调用该函数

1
const [ x, setX ] = useState(createInitialX) //合理的实践

源码