“异步”?
所谓同步还是异步指的是调用 setState 之后是否马上能得到最新的 state
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”
最终决定setState是同步渲染还是异步渲染的关键因素是ReactFiberWorkLoop
工作空间的执行上下文.
什么时候是”异步”
legacy模式下:命中batchedUpdates时是异步 未命中batchedUpdates时是同步的
concurrent模式下:都是异步的
函数组件里使用useState都是异步的
1//以下写法都会是”异步“效果2//所有打印的num都是03export default () => {4 const [num, setNum] = useState(0);5 const handerClick = () => {67 setNum(num + 1);8 console.log(num);9 setNum(num + 1);10 console.log(num);11 setNum(num + 1);12 console.log(num);1314 setTimeout(() => {15 setNum(num + 1);16 console.log(num);17 setNum(num + 1);18 console.log(num);19 setNum(num + 1);20 console.log(num);21 }, 1000);2223 Promise.resolve().then(() => {24 setNum(num + 1);25 console.log(num);26 setNum(num + 1);27 console.log(num);28 setNum(num + 1);29 console.log(num);30 });31 };3233 useLayoutEffect(() => {34 const dom = document.getElementById("button");35 if (!dom) return;36 dom.addEventListener("click", handerClick);37 }, []);3839 return (40 <div>41 <button id="button">{num}</button>42 </div>43 );44};
什么时候是同步
- class组件原生事件触发的setState就是同步的
- class组件在setTimeout里是同步的
- class组件在promise.then里是同步的
1// 以下表现就是同步的2// 每一步会打印不同的值3export default class Index extends React.Component {4 constructor(props) {5 super(props);6 this.state = {7 number: 0,8 };9 }10 handerClick = () => {11 setTimeout(() => {12 this.setState({ number: this.state.number + 1 });13 console.log(this.state.number);14 this.setState({ number: this.state.number + 1 });15 console.log(this.state.number);16 this.setState({ number: this.state.number + 1 });17 console.log(this.state.number);18 }, 1000);19 Promise.resolve().then(() => {20 this.setState({ number: this.state.number + 1 });21 console.log(this.state.number);22 this.setState({ number: this.state.number + 1 });23 console.log(this.state.number);24 this.setState({ number: this.state.number + 1 });25 console.log(this.state.number);26 });27 };2829 render() {30 return (31 <div>32 <button onClick={this.handerClick}>num++</button>33 </div>34 );35 }36}
如何手动合并多次更新
ReactDOM.unstable_batchedUpdates
为什么?
- 合成事件会走batchUpdate
- 在 setState 的时候react内部会创建一个 updateQueue ,通过 firstUpdate 、 lastUpdate 、 lastUpdate.next 去维护一个更新的队列,在最终的 performWork 中,相同的key会被覆盖,只会对最后一次的 setState 进行更新
- 在批量更新上下文中(比如点击事件对应的处理函数),this.setState 会创建一个update,这个更新对应一个 expirationTime,Sync mode下(默认情况)它的值是1。将这个 update 添加到 该组件对应fiber 的 updateQueue。同时将这个过期时间添加到RootFiber。由于处于批量更新上下文中,这些update不会被执行。 然后继续this.setState,再次创建一个 expirationTime 为 1 的update,将这个 update 添加到 fiber 的 updateQueue…如此反复。 直到退出事件处理函数,然后将批量更新的标志(isBatchingUpdates)设置为false,此时 performSyncWork 执行所有的update。
Tips
批量更新:是一个组件连续 setState 多次,这个组件只更新一次,这与过期时间无关 批处理:同一时间,这个组件应该并发更新多少次,这个和过期时间有关,或者说和 lane 有关