我不允许你还搞不懂react的useState() hook

511 阅读5分钟

前言

不知不觉已经实习了2个月了,今天来和大家一起聊聊在实习过程中碰到的useState钩子。

在React的世界里,管理状态(state)是件大事。React提供了一个叫做useState的小工具,让我们可以在组件里轻松地使用状态。但有时候,我们可能会对状态更新的方式感到困惑。别担心,这篇文章会带你了解React是如何处理状态更新的,特别是一个叫做“批处理”的技巧,并通过一些简单的例子来说明。 image.png

什么是批处理

想象一下,你在餐厅点菜,你不会点一个菜就喊一次服务员,而是会等点完所有的菜再一起告诉服务员。React处理状态更新的方式有点像这样。当你在某个操作中多次更新状态时,React不会立刻一个一个地更新,而是把它们攒起来,等操作完成后一次性处理。这样可以节省时间,提高效率。

怎样连续更新状态

有时候,你可能需要在一个操作中多次更新同一个状态。React允许你这样做,并且会把所有的更新操作排好队,等当前操作完成后再一起处理。

各位看官请看以下代码:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>加三次</button>
    </>
  )
}

在这个例子中,尽管我们三次点击了“加三次”按钮,React会在下一次渲染时一次性把计数器增加3。

更新函数和状态替换

有时候,你可能需要在更新状态后立即替换状态。React允许你传递一个更新函数给setNumber,这个函数会接收当前的状态作为参数,并返回新的状态。

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>
增加数字
</button>

在这个例子中,尽管我们首先增加了5,然后又增加了1,但React会在下一次渲染时一次性处理,所以最终的状态值会增加6。

事件处理完成后的状态更新

React会在事件处理完成后处理状态更新。这意味着,如果你在事件处理中更新了状态,那么在处理完成之前,状态不会改变。

function App() {
  const [count, setCount] = useState(0)
  const [num, setNum] = useState(1)
  async function handleClick() {
    console.log('此时的count值', count);
    setCount(count + 1)
    console.log('第', num, '次点击');
    setNum(num + 1)
    console.log('第一次渲染:', count);
    await delay()
    setCount(count - 1)
    console.log('第二次渲染:', count);
  }
  function delay() {
    return new Promise(resolve => {
      setTimeout(resolve, 1000)
    })
  }

  return (
    <>
      {/* <p>hello world</p>
      <Count></Count> */}
      <button onClick={handleClick}>+1/-1</button>
      {count}
    </>
  )
}

在这个例子中,我们模拟了一个异步操作,setPending首先增加1,然后等待3秒后减少1。

第一次点击(假设pending初始值为0):

count值变化过程:

  1. setCount(count + 1): count 是 0 所以 setCount(0 + 1)
  • react准备在下一次渲染时将count更改为1
  1. setCount(count - 1): count是0 所以 setCount(0 - 1)
  • react准备在下一次渲染时将count更改为-1

尽管你调用了多次 setCount,但在 这次渲染的 事件处理函数中 count 会一直是 0,所以你会交替将count加1(0 + 1)设置成1, 然后再减1(0 - 1)设置成-1。这就是为什么在你的事件处理函数执行完以后,React 重新渲染的组件中的 count 等于 -1而不是0 。

image.png

总结

通过理解React的批处理机制,我们可以更有效地管理状态更新,减少不必要的渲染,提高应用性能。在实际开发中,我们应该充分利用React提供的状态更新函数,确保状态的更新是可预测和一致的。

小挑战

相信到这里大家对react的useState批处理机制已经有了一定的了解,下面大家来一起看一个挑战题巩固一下。

现在,让我们来解决一个实际问题。假设我们正在开发一个艺术市场应用,用户可以为艺术品提交多个订单。我们需要确保“等待”和“完成”计数器的行为符合预期。

import { useState } from 'react';

export default function RequestTracker() {
  const [pending, setPending] = useState(0);
  const [completed, setCompleted] = useState(0);

  async function handleClick() {
    setPending(pending + 1);
    await delay(3000);
    setPending(pending - 1);
    setCompleted(completed + 1);
  }

  return (
    <>
      <h3>
        等待:{pending}
      </h3>
      <h3>
        完成:{completed}
      </h3>
      <button onClick={handleClick}>
        购买
      </button>
    </>
  );
}

function delay(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

在上述代码中,pending计数器可能会出现减少到-1的情况,这是因为在异步等待期间,pending状态可能被多次更新。为了解决这个问题,我们需要确保在异步操作期间,状态更新是正确的。

那么我们怎么来解决这个问题呢?我们可以通过使用更新函数来确保状态的正确更新。

async function handleClick() {
  setPending(p => p + 1);
  await delay(3000);
  setPending(p => p - 1);
  setCompleted(c => c + 1);
}

通过这种方式,我们可以确保即使在异步操作期间,状态的更新也是正确的,避免了出现负数的情况。

本篇文章到这里就结束了,希望这篇文章能帮助你更好地理解React的状态更新和批处理机制。

如果觉得本篇文章对你有所帮助,还请点赞+收藏+评论,谢谢大家。

image.png