hook中的useState连续多次更新 render渲染几次?

发布于 2022-09-13 01:20:07 字数 750 浏览 14 评论 0

初学react 第一次点击minus事件和第二次点击事件的执行顺序不一致,setNum是异步的,为什么第一次先执行setNum,而不是执行 console.log("minus") ?
useState执行一次set就触发render,为什么多次执行set结果也只触发一次render?

烦请有空的大佬帮忙看一下!

 const [num, setNum] = useState(1);

  const minus = () => {
    setNum(prev =>{
      console.log(1);
      return  prev + 1
    })
    setNum(prev => {
      console.log(2);
      return prev + 1;
    })
    console.log("minus");
  }
  return (
    <View className='container'>
      <Text className="name">num:{num}</Text>
      <Button onClick={minus}>计数</Button>
    </View>
  )

代码结果:

image.png

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

无法回应 2022-09-20 01:20:07

注意以下所有代码都是基于react 17.0.2,由于不同版本之间代码有所区别,请确保你使用的是该版本以便于react的行为符合预期

(1) setNum是异步的吗?为什么多次setNum只render了一次

setNum并不是异步的,setNum可以为理解为scheduleUpdateOnFiber,他为一个fiber节点调度更新时是同步的,但是他调度的更新并不是立马执行的而是会推迟到微任务中一并之前所有调度了的更新,这也就是为什么多次setNum只render一次的原因,因为多次update会被reduce成一个,具体原理可以看这里我在其中写了一个简易的react batching逻辑

(2) 为什么首次以会先打印1,而后面几次不会

首先我们得知道react更新的逻辑,每个fiber上都存有一个lanes(一个32位有符号整数,用来当作BitSet使用),如果他大于零就代表他需要进行调度更新,或者需要清除lanes,当第一次渲染完成时fiber并没有额外的更新,所以他的lanes为0,当lanes为零是setNum会直接走优化逻辑,直接算出下一次的state,也就在这时调用了setNum第一个参数里的函数,这就是1minus之前打印的原因,在计算出下一次的state后会和当前的state进行比较如果发现前后state变化了的话,就会继续调度该更新,如果没变在比较后,就能直接return什么都不用干了。注意一旦调度成功,就会在fiber数上留下lanes,所以后续几次的setNum就不会走这个优化逻辑,所以minus一直是最先打印的,因为setNum里面的函数是放在微任务中的

(3) 如何让打印信息符合预期

知道了原理,我们要让他每次都打印的一样就很简单了,如果我们能保证setNum调用时fiber上的lanes为零就一直都会走优化逻辑,所有打印也就都一样了,那们要如何清除lanes呢,我们上面说过如果一个fiber上的lanes大于零就代表他需要进行调度更新,或者需要清除lanes如果一个setState调度了一次更新,但是他传入的nextState和当前的没有发生变化,那么就会在这轮更新中将该fiber上的lanes清除,考虑以下代码,如果在每次点击计数之前都先点击一下 reset fiber lanes按钮就能保证setNum调用时fiber上的lanes总是空的那么他的打印永远都只会是
image.png
详情信息可以看我对react更新时fiber树上lanes变化的分析
更多react原理可以查看我的项目tiny-react

function App() {
  const [num, setNum] = useState(1)
  const [, reset] = useState(1)

  const minus = () => {
    setNum((prev) => {
      console.log(1)
      return prev + 1
    })
    setNum((prev) => {
      console.log(2)
      return prev + 1
    })
    console.log('minus')
  }

  return (
    <div className="container">
      <div className="name">{num}</div>
      <button onClick={minus}>计数</button>
      <button
        onClick={() => {
          reset(1)
        }}
      >
        reset fiber lanes
      </button>
    </div>
  )
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文