第 19 题:React setState 笔试题,下面的代码输出什么?
1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。
2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。
3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。
输出: 0 0 2 3
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(22)
恕我直言, 这其实就是react的bug, 整出这么大一堆概念, 就是想让你们接受这个bug, 不要踩这个坑
应该是setVal((val) => val+1)
为什么不是 0 0 3 4
react的state是为view服务的,state到view是响应式的,这种代码看瞎眼
我刚开始得到结果也是0034,看楼主的解答,是前两次
setState
触发了批量更新,两次setState
合并成了一次.在16.8.6源码中调试来看,这题和批量更新是没有关系的,通常批量更新是发生在react事件机制中即触发了事件回调会设置批量更新的标志。从源码中看,该题目的解释是:当前已经处于渲染的阶段,在这个过程中,框架会主动去合并在这期间触发的更新。
react的渲染主要包含两个阶段,render阶段和commit阶段,这两个阶段的包含在performWorkOnRoot方法中,该方法设置isRendering变量,表示当前是否处于渲染阶段。
react的setState可以认为是微任务的一次执行,但是我有点不太明白,setTimeout内的setState为什么没有批量更新?
对于 非concurrent 模式 答案 0023 屏幕显示 0 1 2 3
组件创建 屏幕显示 val 为 0
componentDidMount 执行
注意:(对于前两次之所以都是0 , 并不是 因为 isBatchingUpdates 为 true, 而是还在处理渲染状态,(只有事件系统等触发的才会isBatchingUpdates设置为true)所谓的渲染状态 也就是源码 isRendering 是 true , 这个变量是 虚拟DOM构建过程以及commit过程中都会维持 true 的状态。)
(1)第一个setState触发,创建一个 调度任务,此时 调度链表中有 初始化创建的调度任务和这一次的调度任务。这次调度的过期时间因为是 非concurrent,所以是 sync。将 {val: 1} 对象放入到调度的updateQueue中。此时并不会马上执行,因为 第一个创建的调度还没有结束 isRendering 还是true。所以不会进入渲染阶段,屏幕还是 0.
(2)然后打印 0.
(3)第二个setState触发,创建一个调度任务, 此时的调度链表中有三个调度任务,分别为 初始化的时候调度、第一次setSate、第二次setSate. 这次调度的过期时间因为是 非concurrent,所以是 sync。将 {val: 1} 对象放入到调度的updateQueue中。同理 isRendering 还是 true,屏幕显示还是0.
(4) 然后打印0.
(5)初始化结束开始,isRendering 设置成 false。第一个初始化调度从调度链表中清除,开始进入处理剩下调度任务过程, 然后由于两次的调度任务一致, 所以,优先级一样,先处理第一次的调度任务,由于 第一次 setState 与第二次 优先级都一样, 所以在 render 阶段 收集 更新的时候都被处理了。
(6)屏幕显示 1(并不是 两次 setState 都会输出,而是由于过期时间都一样在 render 的收集更新的时候被处理调了)
setTime 执行
(1)由于此时 isRendering 是false, 所以不会批量更新,而是第三个 setState 调用的时候,过期时间为 sync。 然后创建调度,然后直接进入更新。
(2) 打印输出2. 屏幕显示 2
(3)同理,第四个setState 同步执行
(4)打印输出 3, 屏幕显示 3
对于 concurrent 模式 正常60hz刷新率情况下 答案 0011 屏幕显示 1 2
组件创建,进入调度执行,因为第一个创建的调度任务 肯定是在一帧空闲时间内执行的, 但是可能在 commit 阶段 检测到没有剩余时间了, 所以不会绘制到屏幕上。此时 isRendering 还是true
componentDidMount 执行
(1)执行第一个 setState, 此时 isRendering 是 true,仅仅是创建 调度任务 过期时间是一个异步的时间戳。并不会执行。
(2)打印出 0
(3)执行第二个 setState, 此时 isRedndering是true, 也是不会执行, 仅仅市创建调度任务, 注意的是,这里的异步过期时间与第一次的相同(因为 都是在一个 rendering 中)。
(4)打印出 0
(5)此时调度到第二个setState 的 调度任务的时候, 调度时间早已经过期了,所以退化成 同步的方式直接执行,屏幕绘制出 1。
setTimeout 执行
(1)第三个setState,进入到下一个时间循环的时候,在这之前可能执行了一次 state 变更,变成了1 所以 第三个创建调度任务的时候 val 可能是 2(只能说是可能, 因为不同的刷新率 和 组件大小都会影响了上一次的setState, 但是在目前实例仅仅有一个组件的情况下,state 肯定变成了 1 的因为必然执行了 render 过程)
(2)打印出 1(也是可能值,只不过在 60hz 刷新率下 1 的概率最大)
(3)第四个setState,创建调度任务,此调度任务放入队列,该过期时间可能与第三次的过期时间相同,因为异步计算时间都是在50ms 内的都作为一个过期时间。因为 两个过期时间一致, 所有中断了第三次的更新,进而更新第四次的。
(4)打印出 1
(5)屏幕显示 2
因为你开启了 concurrent 模式 见我的回复
我打印的 0 0 1 1
react-hooks时代貌似改写了这种方式,我用react-hooks写法写出来的结果都是0
@LastStranger
更新的方式没有更改,首先是因为useEffect函数只运行了一次,其次setTimeout是个闭包,访问到的值一直是0(按照正常的写法setVal(val+1))。以例子来看的话,并没有执行更新的操作。
具体可以参考 issue讨论: facebook/react#14010
做成了 0022 哎。竟然有isBatchingUpdates这种东西 学习了 @yhlben 。
题目呢???
很好理解的问题 正常情况下 react是会同步更新数据的 也就是说isBatchingUpdate触发变成true 但是 如果你将state的状态值加入到setTimeout的延迟处理队列中 打印出来的值就是setState之后的值,或者你同时调用setState的第二个参数 也会做到异步操作 获取设置之后的state value
@azl397985856 在React的setState函数实现中,会根据一个变量 isBatchingUpdate 来判断是直接同步更新this.state还是放到队列中异步更新 。React使用了事务的机制,React的每个生命周期和合成事件都处在一个大的事务当中。在事务的前置钩子中调用batchedUpdates方法修改isBatchingUpdates变量为true,在后置钩子中将变量置为false。原生绑定事件和setTimeout异步的函数没有进入到React的事务当中,或者当他们执行时,刚刚的事务已近结束了,后置钩子触发了,所以此时的setState会直接进入非批量更新模式,表现在我们看来成为了同步SetState。
哦也我做对了
具体可以见 ReactFiberScheduler 中的 performWork 和 performSyncWork。 楼主说的基本是对的,如果有这种 async 的 work 就不执行 batch update 如果没有 async 的就执行 batch update,setTimeout 和 promise 这些要进入 EventLoop 队列的都会被认为是 async work。
#17
我理解的是:isBatchingUpdates 默认值为 false,当 react 自身的事件处理函数或 react 生命周期触发时,isBatchingUpdates 会被赋值为 true,当更新完成时又会被复原为 false。 @code-coder
请问一下大佬,isBatchingUpdates的判断条件是什么呀?