刚学习 React 请教一个 useState 有关的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
whoami9426
V2EX    React

刚学习 React 请教一个 useState 有关的问题

  •  
  •   whoami9426 2023-12-10 13:33:17 +08:00 3325 次点击
    这是一个创建于 739 天前的主题,其中的信息可能已经有所发展或是发生改变。

    代码如下:

    import React from 'react'; import { useState, useEffect } from 'react'; export default function App() { const [arr, setArr] = useState([0]); useEffect(() => { console.log(arr); }, [arr]); const handleClick = () => { Promise.resolve() .then(() => { setArr(prevState => [...prevState, 1]); }) .then(() => { Promise.resolve() .then(() => { setArr(prevState => [...prevState, 2]); }) .then(() => { setArr(prevState => [...prevState, 5]); }) .then(() => { setArr(prevState => [...prevState, 6]); }); }) .then(() => { setArr(prevState => { setArr(prevState => [...prevState, 4]); return [...prevState, 3]; }); }) .catch(e => { console.log(e); }); }; return ( <> <button OnClick={handleClick}>change</button> </> ); } 

    点击按钮后,控制台的结果显示为

    (1) [0] (2) [0, 1] (5) [0, 1, 2, 3, 4] (6) [0, 1, 2, 3, 4, 5] (7) [0, 1, 2, 3, 4, 5, 6] 

    想知道为啥结果不是[0, 1, 2, 5, 6, 3, 4],以及如何在 Promise 链中正确调用 setState,感谢大家的指点

    第 1 条附言    2023-12-10 14:10:03 +08:00
    附上代码的在线执行地址吧: https://playcode.io/1690189
    第 2 条附言    2023-12-11 10:54:35 +08:00
    • 感谢大家的指导和回复
    • 确实这应该是一个 Promise 的问题而不是 useState的问题,标题有点误导大家了
    • 我本人是个前端小白,学习 useState 时看到了这种用法可以解决我遇到的问题(出自 React Hook 中 useState 异步回调获取不到最新值及解决方案 ) 结果测试过程中写出了这段诡异的代码,hhh
    16 条回复    2023-12-11 18:00:24 +08:00
    lavvrence
        1
    lavvrence  
       2023-12-10 13:42:35 +08:00
    setState is an async function.
    okakuyang
        2
    okakuyang  
       2023-12-10 13:50:29 +08:00
    你第二个 then 开始就有问题了
    whoami9426
        3
    whoami9426  
    OP
       2023-12-10 13:56:16 +08:00
    @jaylee4869 这个我知道,但是为啥和 then 结合在一起感觉没有达到编码的预期效果
    whoami9426
        4
    whoami9426  
    OP
       2023-12-10 13:57:43 +08:00
    @okakuyang 是 Promise 嵌套的问题吗?
    whoami9426
        5
    whoami9426  
    OP
       2023-12-10 13:59:02 +08:00
    附上代码的在线执行地址吧: https://playcode.io/1690189
    common0
        6
    common0  
       2023-12-10 14:32:50 +08:00   1
    第二个 then() 里的 Promise.resolve() 前加上 return ,5 、6 就在 3 、4 前面了。
    wipbssl
        7
    wipbssl  
       2023-12-10 14:37:58 +08:00   2
    异步逻辑的问题,第二个 then 相当于 async 函数里放了一个 aync 函数,但没有加入 await ,所以执行到 setArr 2 时就异步,主线程继续执行第三个 then 了,5 和 6 要等到 2 的 promise resolve 后才会执行,但任务序列中第 3 个 then 在 setArr 2 resolve 前就加入队列了
    chenliangngng
        8
    chenliangngng  
       2023-12-10 15:22:06 +08:00   1
    Promise 立即执行,当前轮宏任务
    then 当前轮微任务
    useState 浏览器空闲,也就是上一轮执行完了,下一轮宏任务
    then 下一轮的微任务
    onec
        9
    onec  
       2023-12-10 15:36:27 +08:00
    第一个 Promise.resolve()后立即执行 then1 回调,then1 返回 pending promise ,then2, then3 依次进入队列br />then2 回调开始执行,then2.1 立即执行返回 pending promise, then2.2, then2.3, then2.3 进入队列
    then3 执行
    then2.2 执行
    then2.3 执行
    done
    SilencerL
        10
    SilencerL  
       2023-12-10 17:55:37 +08:00   1
    简化一下你的代码:

    Promise.resolve()
    .then(() => {
    setTimeout(() => console.info(1))
    })
    .then(() => {
    Promise.resolve()
    .then(() => {
    setTimeout(() => console.info(2))
    })
    .then(() => {
    setTimeout(() => console.info(5))
    })
    .then(() => {
    setTimeout(() => console.info(6))
    });
    })
    .then(() => {
    setTimeout(() => {
    console.info(3)
    Promise.resolve().then(() => console.info(4))
    })
    })

    进一步简化:

    Promise.resolve()
    .then(() => {
    console.info(1)
    })
    .then(() => {
    Promise.resolve()
    .then(() => {
    console.info(2)
    })
    .then(() => {
    console.info(5)
    })
    .then(() => {
    console.info(6)
    });
    })
    .then(() => {
    console.info(3)
    // console.info(4) // 1 2 3 4 5 6
    // Promise.resolve().then(() => console.info(4)) // 1 2 3 5 4 6
    // setTimeout(()=>console.info(4)) // 1 2 3 5 6 4
    })

    你这个问题可以简化成和 React 没任何关系的问题,纯粹是浏览器任务队列的问题,Promise.resolve().then 可以生成一个微任务,setTimeout 或者你问题中的 setArr 生成的时宏任务(现代浏览器没得宏任务了,分成了更多任务列表,但是为了方便解释就还是说宏任务)

    你可以观察进一步简化后的版本,以及看一下最后一个 then 里面关于不同方式 4 的输出时机,尝试理解一下。

    但是不得不说,理解起来可能很困难,你需要了解 js 的事件循环以及队列优先级的问题。

    大概来说,微任务优先级高,宏任务优先级低,每次事件循环按照优先级拿一遍任务。

    - 最顶层的 Promise.resolve().then -> console.info(1) 立刻输出 1

    - 第二个 then
    -- 第二个 then 里面 Promise.resolve().then -> console.info(2) 立刻输出 2
    -- 第二个 then 里面 Promise.resolve() 的第一个 then 作为一个微任务已经结束,后续的第二个 then 扔到下一次微任务队列中

    - 第三个 then
    -- 第一句 console.info(3) 立刻输出 3
    -- 第二句:
    --- 情况 1:console.info(4) 那就立刻输出 4
    --- 情况 2:Promise.resolve().then(() => console.info(4)) 扔一个微任务到下次事件队列,任务是 console.info(4)
    --- 情况 3:setTimeout(()=>console.info(4)) 扔一个宏任务到下次事件队列,任务是 console.info(4)

    - 下一次循环
    -- 微任务队列有一个
    .then(() => {
    console.info(5)
    })
    .then(() => {
    console.info(6)
    });
    --- 这里你可以看成一个新的
    Promise.resolve()
    .then(()=> console.info(5))
    .then(() => {
    console.info(6)
    });
    (当然这不能真的这么看,但是为了讲解方便,你就这么理解好了。。。)
    --- 所以立即执行了这个微任务 console.info(5),把 console.info(6) 继续扔到微任务队列
    -- 如果上一次循环的第三个 then 里面情况 2 ,那么在上一步的 5 输出结束后 6 的前面就有一个上一次扔过来的的微任务:console.info(4)
    -- 如果上一次是情况 3 ,输出 4 这个任务在宏任务队列,那么就先不管他,把当前下一个微任务输出 6 执行,再去执行宏任务队列 console.info(4)

    单纯文字讲的讲不清楚,你要实际用代码多试几次,尽可能简化代码并且尝试不同的 case ,才能大概理解这个幺蛾子事件队列

    顺便感谢你提供一道好玩的面试题,下次可以拿去为难其他人(
    otakustay
        11
    otakustay  
       2023-12-10 21:48:26 +08:00
    你这代码就是改成 console.log 也是 0, 1, 2, 3, 4, 5, 6 吧,和 state 一点关系都没有,纯 Promise 执行顺序问题
        12
    8XIQz5SCHX1U6c7s  
       2023-12-11 10:09:35 +08:00
    好好好,又复习了一遍事件循环
    lilei2023
        13
    lilei2023  
       2023-12-11 10:12:38 +08:00
    这和 setState 没关系吧,这不就是 promise 执行问题么
    lilei2023
        14
    lilei2023  
       2023-12-11 10:25:31 +08:00
    @lilei2023 不过我也做错了
    Leeeeex
        15
    Leeeeex  
    PRO
       2023-12-11 16:15:38 +08:00
    有意思,所有的 ai 都认为答案是 [0, 1, 2, 5, 6, 3, 4]
    CrispyNoodles
        16
    CrispyNoodles  
       2023-12-11 18:00:24 +08:00
    好好好,这样玩是吧。本来写完代码只是想摸下鱼,强制让我复习了一遍事件队列,内存,栈,堆的概念
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4618 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 05:38 PVG 13:38 LAX 21:38 JFK 00:38
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86