搞不懂 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 条回复
visper
308 天前
可能题主的奇怪点在于,如果那些函数都是放在队列里面等时机再执行,那应该最后的 A state1 就会在 A prev 1 前面。这样卡在中间的话不知道 react 是什么执行规则的。
JounQin
308 天前
不要学八股文
codehz
308 天前
要不你试试不同版本的区别(我感觉这应该有差异
codehz
308 天前
有没有一种可能和 Fiber Reconcilier 的调度有关系
oatw
308 天前
感觉很多兄弟没审题就质疑楼主呢?

没看过这部分源码,盲猜可能是这样:

1. 第一次调用 set 的时候通知 react 要更新了;
2. 在准备 rerender 前,其它 set 操作会被入队,等待一起执行;
3. 执行所有队列中的 set 操作&rerender 。

我觉的关键就是 react 总得知道要更新这件事,而这里就是第一次 set 触发的,里面的函数也真的执行了,然后在真正更新前发现还有其他 set ,所以就入队了其他 set 操作。
chenliangngng
308 天前
@dcsuibian 在触发更新前,变更函数都会立即执行,因为只有执行了 react 才知道你是否变更,你返回相同值试试。在发生变更之后,剩下的变更函数全部打到更新队列里去
oatw
308 天前
@oatw 补充一下,什么时候执行 set 内函数其实并不是关键,即便执行了,也只是计算了结果暂存下来,并没有真的更新状态变量,状态变量都是在队列中所有函数执行后的最终计算结果下更新。但是私以为第一次 set 的时候内部的函数也可以不立刻执行,而是放到队列中和其他的一起执行,至于为啥,只能读源码了。官方文档也不完全可信。
johnnyNg
308 天前
react 的 hooks 确实很反直觉,我只喜欢 react'的 jsx ,vue'的 watch 、computer 就感觉很符合直觉
swaggeek
308 天前
@oatw 我看了下 React 文档,他是需要在 render 的时候再去看看队列里有没有 set 函数,然后再执行取值的。感觉这样其实 react 会有很多次无效渲染。像 Vue 就有合并状态的这种操作,最终只会更新一次。
ljpCN
308 天前
[今天让你彻底搞懂 setState 是同步还是异步]( https://zhuanlan.zhihu.com/p/350332132)
swaggeek
308 天前
@swaggeek 我没怎么使用过 react hook ,我是写 Vue 的,不过我感觉 react 这样的设计还是挺奇怪的。
oatw
308 天前
@swaggeek 啊!我不知道呀。纯粹是根据经验盲猜的。React 和 Vue 我都没兴趣,前端太难了,我不配,哈哈哈~
helIo0o
308 天前
用同样的代码试了一下,
第一次点击按钮时,打印顺序:
A prev1 1
A state 1
A prev2 2
A prev3 3
A prev4 4
A render 5

后面再点击按钮时,打印顺序:
A state 5
A prev1 5
A prev2 6
A prev3 7
A prev4 8
A render 9

prev1 那里只在第一次点击的时候,才会同步执行一次,后面都是异步的了
dcsuibian
308 天前
@helIo0o 确实,这个我倒没注意到。
mikami
308 天前
[react-hooks-usestate-set-function-exhibits-sync-as-well-as-async-behaviour]( https://stackoverflow.com/questions/72200664/react-hooks-usestate-set-function-exhibits-sync-as-well-as-async-behaviour)

真想要搞明白为什么这个顺序,我估计只能看源码了。只能说日常工作中不要这么写
wpzz
308 天前
"
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”.
"

如果某些内容在屏幕外,我们可以延迟与其相关的任何逻辑。如果数据到达速度快于帧速率,我们可以合并并批量更新。
Leviathann
308 天前
react 是协程式的执行方式,不要依赖它调用顺序
Chingim
308 天前
好问题, 值得写一篇文章
vincenteof
308 天前
这应该是实现细节,说不定哪个版本以后就变了。updater 如果是 pure function 的话,你可能不需要关心它到底什么时候被执行?
wpzz
308 天前
@wpzz 是这里吧,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<any> = [];
let concurrentQueuesIndex = 0;

let concurrentlyUpdatedLanes: Lanes = NoLanes;


而且开发模式下不太准, 源码中有很多地方判断了__DEV__

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

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

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

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

© 2021 V2EX