搞不懂 useState 为什么会这么设计?

308 天前
 dcsuibian

以下代码

import {useState} from "react";

export default function StateUpdate() {
    const [A, setA] = useState(1)
    console.log('A', 'render', A)
    return (
        <div>
            <button onClick={() => {
                setA(prev => {
                    console.log('A', 'prev1', prev)
                    return prev + 1
                })
                setA(prev => {
                    console.log('A', 'prev2', prev)
                    return prev + 1
                })
                setA(prev => {
                    console.log('A', 'prev3', prev)
                    return prev + 1
                })
                setA(prev => {
                    console.log('A', 'prev4', prev)
                    return prev + 1
                })
                console.log('A', 'state', A)
            }}>
                button A {A}
            </button>
        </div>
    )
}

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

A state 1正正好好卡在中间,虽然此时状态还是 1 。但说明了 prev1 这个函数是立即执行的。

我真的搞不懂为什么要这么设计?

5089 次点击
所在节点    React
56 条回复
Pencillll
308 天前
https://github.com/reactjs/react.dev/issues/5982#issuecomment-1674183863

> The timing for when the callback is called is not guaranteed.
charlie21
308 天前
在点击事件之后最新渲染值,Vue 和 React 的这个行为各是出于什么考虑?
https://www.zhihu.com/question/543057656
realJamespond
308 天前
估计是 setState 第一次直接执行,第二次以后放入队列?这样强行解释满意不😂
ragnaroks
308 天前
react fiber 更新会“自己”选择每批执行的逻辑,同步也好异步也好没法在代码层面保证执行时机,这个设计的目的是确保在大多数情况下不会低于 60 帧,实际上还是看编码人员的水平。

循环 setState 只需要纳秒单位的时间,理论上只会显示最新值,实际上如果机器配置足够低,是能看到中间值的。
Alander
308 天前
似乎楼内没有回答到点子上的,去 github 拉一下 react 源码就知道为什么了,useState 返回的数组的第 1 个对象是个 dispatch 方法,该方法伪代码如下:
```js
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update)
} else {
doAction()
enqueueRenderPhaseUpdate(queue, update)
}

```
第一次 setState 是在 else 里面会调用 action 就是打印 A prev1 1 ,后续的几次其实因为本次渲染应该在微任务队列所以走到 if 里面
Alander
308 天前
具体代码看 ReactFiberHooks.js 里的 dispatchSetState 方法吧,还是比较清晰明了的,日常还遇到过很多奇葩的问题其实去看一眼源码就大概了解了,react 还是比较线性的
Mikawa
308 天前
OP 的 React 是哪个版本的

这里 onClick 是个合成事件的回调,可能是受到 Reconcilier 的影响了,用 addEventListener 试试呢
mxT52CRuqR6o5
308 天前
严格模式且开发模式下 setState 后会执行一次 render 检查用户的 useEffect 是否有正确清除副作用
mxT52CRuqR6o5
308 天前
AQingC
308 天前
楼主 react 版本是多少?
royzxq
308 天前
这和 fiber 的实现有关.. 具体的话可能要看源码
rrfeng
308 天前
楼上有人回答了,我半吊子 js/react 水平,但是我知道
setState 和 console.log 都是异步的,所以我不能相信任何它的顺序。
xuhai951753
308 天前
如果你想问为什么这么设计,就是为了避免大量的渲染,否则每次 set 都会 render 一遍。你可以在 react 非 concurrent 模式下将 onClick 的内容在 setTimeout 中执行做对比。
如果你想问实现,简单来说在合成事件或者 concurrent 模式中第一次 set 之后就会打个标,标记要 update ,后续的所有 set 就会合并到更新时执行,至少是下一个时间切片。
HTML001
308 天前
笑了,前面几楼一上来就嘲讽,但是连问题都没看明白
zangbianxuegu
308 天前
@Pencillll 大佬是怎么快速搜索/定位到问题解释的呢?
Chingim
308 天前
写了篇文章分析: https://www.overcch.com/posts/useState-callback-timing

TLDR;
因为性能优化, 如果当前 fiber 没有更新, react 会先执行 setState 的回调, 用来做性能优化: 如果 setState 的结果和当前值一致, 就跳过后续的 reconcile 和 render 过程.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1006034

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX