先来看看这样一个场景。
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1)
console.log(count)
}
return (
<div>
<button onClick={handleClick}>{count}</button>
</div>
)
}
按钮最开始显示为 0
第一次点击之后显示为 1
第二次点击之后显示为 2
......
看起来,一切都跟预期设想的一样。但是打开控制台,会发现在第一次点击之后,按钮显示为 1 而控制台输出的是 0。是代码哪里写错了吗?明明把count
的状态修改为 1,为什么控制台还是输出 0 呢?
其实代码并没有什么问题,而是一开始我们还没有认清 React 的渲染机制。通俗来讲,React 属于状态驱动渲染。只要状态改变,就会触发渲染;而新的渲染,又会携带新的状态。
重新看回上面的代码,不过这次换一种方式,我们把count
换成数值。(并非真的改成数值,只是一种便于理解的视角)
// 初次渲染时count 为 0
export default function Counter() {
const [count, setCount] = useState(0)
function handleClick() {
setCount(0 + 1) // count 将会被修改为 1
console.log(0)
}
return (
<div>
<button onClick={handleClick}>{0}</button>
</div>
)
}
第一次点击按钮后,会修改count
的状态,并触发第一次重新渲染。
// 第一次重新渲染时count 为 1
export default function Counter() {
const [count, setCount] = useState(1)
function handleClick() {
setCount(1 + 1) // count 将会被修改为 2
console.log(1)
}
return (
<div>
<button onClick={handleClick}>{1}</button>
</div>
)
}
同理,第二次点击按钮之后...
// 第二次重新渲染时count 为 2
export default function Counter() {
const [count, setCount] = useState(2)
function handleClick() {
setCount(2 + 1) // count 将会被修改为 3
console.log(2)
}
return (
<div>
<button onClick={handleClick}>{2}</button>
</div>
)
}
为什么能把 count 换成上面的数值呢?这是因为状态是不可变的,它其实就是一个常量,组件的每一次渲染都有属于它们自己的状态。也就是说,在函数式组件在完成渲染返回 JSX 之前,count
会一直保持原来的数值。只有在重新渲染时,函数式组件才会以新的状态来渲染视图。
再来看看下面的例子。
setCount(count += 1) // 编辑器会报错
改成这种写法之后,编辑器会告诉你'count' is constant
。我们并不能修改count
的数值,只能够给setter
函数传入新的数值。这也很好地印证了我们上面的看法。
所以,请记住“state 如同一张快照”。setter
函数不会修改已有的state
,只是触发重新渲染,而新的渲染会给你带来新的state
。