当我学习React时,每个人都告诉我setState
是异步的,因为如果我console.log(state)
正好在setState
之后,它将打印旧值。然而,当我检查源代码时,useState
没有返回任何promise,也没有使用async/await。
意味着setState
必须同步。但是,如果它是同步的,那么为什么React在我调用setState时不立即重新渲染组件?正如您在下面的代码片段中看到的,首先打印0
,然后打印render1
。
如果这是因为批处理,那么批处理是如何工作的?怎么可能";等待";直到触发App组件重新渲染的特定时间。更重要的是,React如何知道在这种情况下需要调用App而不是其他函数?
export default function App(){
const [count, setCount] = useState(0);
console.log("render", count);
return <button onClick={() => {
setCount(count+1); // why doesn't App get called/re-rendered right away here
console.log(count); // this prints the old value first then re-render happens later
}}>
</button>
}
首先,我要提醒您,console.log(count)
注销旧值的原因与sync与async无关。注销旧值的原因是count
是本地const
,它永远不会更改。它以旧价值观开始存在,无论时间流逝多少,无论操作顺序如何,它都将永远是旧价值观。
调用setCount
不会而更改计数中的值,它只是要求react重新提交组件。当新的渲染发生时,将创建一组具有新值的新局部变量。但是上一次渲染的console.log无法与下一次渲染中的值交互。
也就是说,react中的渲染确实(通常(会短暂延迟,以批量处理多个更改。我不确定他们做这件事的确切方式,因为有几种可能性。我以前看过他们的代码,但在过去几年里发生了足够多的变化,我怀疑我的发现是否仍然有用。在任何情况下,他们可以批量重新发送的方式包括:
-
setTimeout
。当你第一次设置状态时,他们可能会设置一个超时,很快就会熄灭。如果发生另一个设置状态,他们会注意到该状态,但不必设置额外的超时。稍晚一点,超时将停止,渲染将发生。 -
微任务。最简单的方法是
Promise.resolve().then(/* insert code here */)
。一旦当前调用堆栈完成执行,微任务就会运行。这种方法的优点是它可以比setTimeout
更快地发生,因为超时具有最小的持续时间 -
如果执行开始于react内部,他们可以等到代码返回后再进行渲染。这曾经是反应批处理更改的主要方式。例如,他们有一段代码负责调用
useEffect
s。在此期间,它会记录状态变化,但还没有重新发送。最终您返回,由于返回将代码执行重新交给react,它会检查哪些状态已更改,并在需要时重新发送。
React如何知道在这种情况下需要调用App而不是其他函数?
它知道这一点,因为您更改的状态是App
的状态。React知道组件的树结构,所以它知道它只需要呈现您设置状态的组件及其子级。