以下代码
import {useState} from "react";

export default function StateUpdate() {
const [A, setA] = useState(1)
console.log('A', 'render', A)
return (

)
}

点击一下按钮后的执行结果是什么?

A state 1正正好好卡在中间,虽然此时状态还是 1 。但说明了 prev1 这个函数是立即执行的。
我真的搞不懂为什么要这么设计?

你没学过编程么,异步不懂?

调用 prev1 的时候组件更新然后后面的 set 函数都会进入等待更新队列,这是时候执行 console 所以输出了 state 。后面的就进行合并更新了。对于合并更新参考 juejin.cn/post/7228851979462737978

react.dev 先读一遍

你有看我的内容吗? prev1 这里明显是被同步的调用了(虽然状态没更新),而其他的都是异步的。

state 1 那是因为这里是个闭包,底层的 A 其实已经改了

上面说的很清楚啦。看过一条推文大意是很多人懂 js 所以就下意识以为也懂 React ,实际上并没有仔细看过文档。一开始写 react 我也是,结果踩了一堆坑。比如 useEffect 和我以为的 vue watch 根本不一样,还是写 vue 顺手
除了 react 有意将副作用隔离之外还有一个就是语言限制,即你不能修改一个被解构出来的变量(虽然就算你不解构它也不会变)

我不是关心这个值,我是关心为什么这个函数是立即执行了

要先把 react 这个概念先搞懂(范志毅脸)

  1. 打开 Google2. 找到输入框,输入“React useState 原理”3. 按下回车4. 阅读文章

这个应该是 stale closure

Here, n => n + 1 is called an updater function. When you pass it to a state setter:React queues this function to be processed after all the other code in the event handler has run.During the next render, React goes through the queue and gives you the final updated state.

setA 是函数,他映射的 useState 函数是异步函数,console.log('A', 'state', A)展示 1 ,有啥问题?

所以, 你是对时序有要求?如果是我设计的话, 考虑有个 effect B 依赖 A, 那我修改 A 多次, 这个 effect B 是否要执行多次呢? react 重新执行 hook, 我记得是需要再次执行 render 函数的, 碰到你这个场景, 如果 setState 时正在执行 render, 要怎么处理呢?总之这种 依赖 -> 响应 的模式很脆弱, 为了防止出问题, 会加很多限制与异步

zh-hans.react.dev/learn/queueing-a-series-of-state-updates

我对他打印的值一点问题都没有,我有问题的是为什么打印的是:A prev1 1A state 1A prev2 2A prev3 3而不是:A state 1A prev1 1A prev2 2A prev3 3

经典面试题“setState 是异步还是同步”

设值这个过程是一个同步的过程吧,需要执行函数,然后拿到新值,所以立刻执行了,后面的没有立即执行,是因为像 2 楼说的,进入了批量更新的队列,被加了锁了。需要等这次更新完,才能执行,有点像微任务队列吧。

怎么感觉大多都没 get 到 op 困惑点?

可能题主的奇怪点在于,如果那些函数都是放在队列里面等时机再执行,那应该最后的 A state1 就会在 A prev 1 前面。这样卡在中间的话不知道 react 是什么执行规则的。

不要学八股文

要不你试试不同版本的区别(我感觉这应该有差异

有没有一种可能和 Fiber Reconcilier 的调度有关系

感觉很多兄弟没审题就质疑楼主呢?没看过这部分源码,盲猜可能是这样:1. 第一次调用 set 的时候通知 react 要更新了;2. 在准备 rerender 前,其它 set 操作会被入队,等待一起执行;3. 执行所有队列中的 set 操作&rerender 。我觉的关键就是 react 总得知道要更新这件事,而这里就是第一次 set 触发的,里面的函数也真的执行了,然后在真正更新前发现还有其他 set ,所以就入队了其他 set 操作。

在触发更新前,变更函数都会立即执行,因为只有执行了 react 才知道你是否变更,你返回相同值试试。在发生变更之后,剩下的变更函数全部打到更新队列里去

补充一下,什么时候执行 set 内函数其实并不是关键,即便执行了,也只是计算了结果暂存下来,并没有真的更新状态变量,状态变量都是在队列中所有函数执行后的最终计算结果下更新。但是私以为第一次 set 的时候内部的函数也可以不立刻执行,而是放到队列中和其他的一起执行,至于为啥,只能读源码了。官方文档也不完全可信。

react 的 hooks 确实很反直觉,我只喜欢 react'的 jsx ,vue'的 watch 、computer 就感觉很符合直觉

我看了下 React 文档,他是需要在 render 的时候再去看看队列里有没有 set 函数,然后再执行取值的。感觉这样其实 react 会有很多次无效渲染。像 Vue 就有合并状态的这种操作,最终只会更新一次。

今天让你彻底搞懂 setState 是同步还是异步

我没怎么使用过 react hook ,我是写 Vue 的,不过我感觉 react 这样的设计还是挺奇怪的。

啊!我不知道呀。纯粹是根据经验盲猜的。React 和 Vue 我都没兴趣,前端太难了,我不配,哈哈哈~

用同样的代码试了一下,第一次点击按钮时,打印顺序:A prev1 1A state 1A prev2 2A prev3 3A prev4 4A render 5后面再点击按钮时,打印顺序:A state 5A prev1 5A prev2 6A prev3 7A prev4 8A render 9prev1 那里只在第一次点击的时候,才会同步执行一次,后面都是异步的了

确实,这个我倒没注意到。

react-hooks-usestate-set-function-exhibits-sync-as-well-as-async-behaviour真想要搞明白为什么这个顺序,我估计只能看源码了。只能说日常工作中不要这么写

"If something is offscreen, we can delay any logic related to it. If data is arriving faster than the frame rate, we can coalesce and batch updates. We can prioritize work coming from user interactions (such as an animation caused by a button click) over less important background work (such as rendering new content just loaded from the network) to avoid dropping frames.To be clear, we are not taking advantage of this right now. However the freedom to do something like this is why we prefer to have control over scheduling, and why setState() is asynchronous. Conceptually, we think of it as “scheduling an update”."如果某些内容在屏幕外,我们可以延迟与其相关的任何逻辑。如果数据到达速度快于帧速率,我们可以合并并批量更新。

react 是协程式的执行方式,不要依赖它调用顺序

好问题, 值得写一篇文章

这应该是实现细节,说不定哪个版本以后就变了。updater 如果是 pure function 的话,你可能不需要关心它到底什么时候被执行?

是这里吧,filber 并发更新。// If a render is in progress, and we receive an update from a concurrent event,// we wait until the current render is over (either finished or interrupted)// before adding it to the fiber/hook queue. Push to this array so we can// access the queue, fiber, update, et al later.const concurrentQueues: Array = [];let concurrentQueuesIndex = 0;let concurrentlyUpdatedLanes: Lanes = NoLanes;而且开发模式下不太准, 源码中有很多地方判断了__DEV__

github.com/reactjs/react.dev/issues/5982#issuecomment-1674183863> The timing for when the callback is called is not guaranteed.

在点击事件之后最新渲染值,Vue 和 React 的这个行为各是出于什么考虑? www.zhihu.com/question/543057656

估计是 setState 第一次直接执行,第二次以后放入队列?这样强行解释满意不😂

react fiber 更新会“自己”选择每批执行的逻辑,同步也好异步也好没法在代码层面保证执行时机,这个设计的目的是确保在大多数情况下不会低于 60 帧,实际上还是看编码人员的水平。循环 setState 只需要纳秒单位的时间,理论上只会显示最新值,实际上如果机器配置足够低,是能看到中间值的。

似乎楼内没有回答到点子上的,去 github 拉一下 react 源码就知道为什么了,useState 返回的数组的第 1 个对象是个 dispatch 方法,该方法伪代码如下:jsif (isRenderPhaseUpdate(fiber)) { enqueueRenderPhaseUpdate(queue, update)} else { doAction() enqueueRenderPhaseUpdate(queue, update)}第一次 setState 是在 else 里面会调用 action 就是打印 A prev1 1 ,后续的几次其实因为本次渲染应该在微任务队列所以走到 if 里面

具体代码看 ReactFiberHooks.js 里的 dispatchSetState 方法吧,还是比较清晰明了的,日常还遇到过很多奇葩的问题其实去看一眼源码就大概了解了,react 还是比较线性的

OP 的 React 是哪个版本的这里 onClick 是个合成事件的回调,可能是受到 Reconcilier 的影响了,用 addEventListener 试试呢

严格模式且开发模式下 setState 后会执行一次 render 检查用户的 useEffect 是否有正确清除副作用

www.zhihu.com/question/387196401

楼主 react 版本是多少?

这和 fiber 的实现有关.. 具体的话可能要看源码

楼上有人回答了,我半吊子 js/react 水平,但是我知道setState 和 console.log 都是异步的,所以我不能相信任何它的顺序。

如果你想问为什么这么设计,就是为了避免大量的渲染,否则每次 set 都会 render 一遍。你可以在 react 非 concurrent 模式下将 onClick 的内容在 setTimeout 中执行做对比。如果你想问实现,简单来说在合成事件或者 concurrent 模式中第一次 set 之后就会打个标,标记要 update ,后续的所有 set 就会合并到更新时执行,至少是下一个时间切片。

笑了,前面几楼一上来就嘲讽,但是连问题都没看明白

大佬是怎么快速搜索/定位到问题解释的呢?

写了篇文章分析: www.overcch.com/posts/useState-callback-timingTLDR;因为性能优化, 如果当前 fiber 没有更新, react 会先执行 setState 的回调, 用来做性能优化: 如果 setState 的结果和当前值一致, 就跳过后续的 reconcile 和 render 过程.