为什么react要异步setState?
react的更新粒度是应用级别,而且是脏处理。
每次更新都要将整个应用(fiber树)更新流程走一次。
若每个setState
都要直接执行更新应用,性能消耗极大。
于是,有了批处理,让 setState
一批进行处理,减少更新频率。
( setState
** 异步** 其实应该叫做 **延迟更新 **)
批处理。哪一批?怎么处理?
现在,我们有如下组件。
function FC() {
const [state, setState] = useState(0);
const handleClick = () => {
setState((v) => v + 1)
setState((v) => v + 1)
setState((v) => v + 1)
}
return (
<div onClick={handleClick}>{state}</div>
)
}
思考,若我们是开发者,handleClick
时该如何实现批处理?
- 收集3个
setState
,构成链表。 - 在
handleClick
结束后,更新应用,在组件更新时,遍历链表,计算新state
,。
逻辑没问题,但是有几个问题。
怎么收集setState? 存储到呢?
解决存储问题。
每个组件对应构造一个节点,称其为 fiber
。
fiber
创建字段 updateQueue
存放 state更新链表。
收集setState。
在 setState
时,创建一个 udpate
对象,存储着 新state / state更新函数。
然后将其,链接到 updateQueue
的尾部。
什么时机更新应用?
handleClick
结束后,setState
就全部收集了,所以在 handleClick
结束后,更新应用。
怎么才能接管 handleClick
函数?
接管事件,在 onClick
后执行 handleClick
然后在更新应用。
这样** 批处理,"异步"更新就都实现了。**
但是有个 bug
,如果我们在 setTimeou
, AJAX
等 react
无法接管的异步中 setState
,那就永远无法更新了。
function FC() {
const [state, setState] = useState(0);
const handleClick = () => {// 事件执行
setTimeout(()=>{
// 1秒后 setState
setState((v) => v + 1)
setState((v) => v + 1)
setState((v) => v + 1)
// 谁来更新?
},1000)
// 事件结束 更新应用
}
return (
<div onClick={handleClick}>{state}</div>
)
}
为了解决此问题,在无法接管的函数中,setState
后,立即执行更新应用。即同步更新。
setTimeout(()=>{
// 1秒后 setState
setState((v) => v + 1) //更新应用
setState((v) => v + 1) //更新应用
setState((v) => v + 1) //更新应用
},1000)
(concurrent mode 重构为 优先级+调度更新应用,解决此问题,即自动批处理。)
总结
原理一句话简述:收集state,延迟更新应用。 在无法接管函数时,每次setState都会更新应用。
可以理解为,react能掌控的,则是"异步"(延迟更新),无法掌控,则是同步更新。