前言
本人不太了解 React 之前类组件中的 setState
函数,我是直接从 React Hook 入门的 React。网上查阅了其他文章以及视频,对于 setState
类组件函数,状态更新是异步的而不是同步的。
在最新文档(React Hook)中,useState
适用于函数组件,而这一个 API 有了一个新的概念(机制)——快照。具体请阅读:state 如同一张快照。
通篇阅读文档之后,我认为打乱文档原本的顺序解读快照机制会更好。
解读快照机制
在函数组件中,useState
中的 state 是一个快照更新,既不是异步更新也不是同步更新。所谓快照就是 React 在更新函数组件内的 state 时会把那时作为一个节点,函数组件内的所有 state 都是基于那时的节点的。
React 文档的案例中,最能说明快照机制的是下面的例子:
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<h1>{number}</h1>
<button onClick={() => {
setNumber(number + 5);
setTimeout(() => {
alert(number);
}, 3000);
}}>+5</button>
)
}
点击之后,在异步任务之前已经更新了 number,那么 alert(number)
打印出来的应该是 5,因为异步任务已经推迟了 3s 才运行:
很明显,不符合我们预想的那样。这已经充分说明,在函数组件中的 state 并不是类组件那样的异步更新,而是快照更新:一切基于那时的时间节点。
仔细发现,UI 及时地更新了 state。这其中的内在逻辑,需要研究源码才可知晓。受水平限制,无法研究 React 源码进行解读快照机制的内核。
更形象的比喻
快照可以比作是一个相机,拍下那个时候的照片,所有的状态都停留在了照片上。因此,函数组件内的所有 state 也就停留在了 update
那个时候的节点上。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
function update(n) {
setNumber(n);
alert(number);
}
return (
<>
<h1>{number}</h1>
<button onClick={() => {
update(1);
update(1);
update(1);
}}>+1</button>
</>
)
}
因为快照机制,此时的 number 已经停留在 update
之前的状态了(快照已经生成),即便是有三个 update
在执行,它们都是基于 number
为 0 的状态 +1 的。因此,UI 上只会出现 1。
这一个例子是文档:渲染会及时生成一张快照 。
更新批处理
如上面的例子中,update
函数执行了三次,它们是批量更新的,不管有多少个,UI 呈现出来的结果是最后一次 update
执行的结果。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
function update(n) {
setNumber(n);
alert(number);
}
return (
<>
<h1>{number}</h1>
<button onClick={() => {
update(1);
update(2);
update(3);
}}>+1</button>
</>
)
}
状态更新函数
实现基于前一个函数执行的结果,打破快照机制,改用状态更新函数。
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button onClick={() => {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>+3</button>
</>
)
}
setNumber
传递一个函数,箭头函数参数 n 是上一次执行的结果,而不是快照机制保留下来的固定的 number。
综上所述
函数组件的 state 快照机制非常神奇,不是同步更新、也不是类组件的异步更新,而是一种快照更新。