V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
whoami9426
V2EX  ›  React

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

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

    代码如下:

    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
    jaylee4869
        1
    jaylee4869  
       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 依次进入队列
    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 执行顺序问题
    8XIQz5SCHX1U6c7s
        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 不过我也做错了
    chanChristin
        15
    chanChristin  
       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   ·   实用小工具   ·   1105 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 18:53 · PVG 02:53 · LAX 10:53 · JFK 13:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.