react-setState两种传参方式

loading 2023年12月29日 35次浏览

以下内容皆基于react18

setState有两种形式,一种是就传个新的State进去,像这样:

setState(n + 1)

另一种是传一个函数进去,像这样:

setState(n => n + 1);

1. 直接传参

区别在哪?来看个例子:

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

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

这个例子中每次点击button,number都只会+1,为什么?

因为state实际上存的是当前渲染的快照,也就是说这次渲染中的setState,在下次渲染中才能体现出来,因此实际上这段代码的三行setNumber()是被这么处理的:

  • setNumber(number + 1):number 是 0 所以 setNumber(0 + 1)。
    React 准备在下一次渲染时将 number 更改为 1。

  • setNumber(number + 1):number 是0 所以 setNumber(0 + 1)。
    React 准备在下一次渲染时将 number 更改为 1。

  • setNumber(number + 1):number 是0 所以 setNumber(0 + 1)。
    React 准备在下一次渲染时将 number 更改为 1。

2. 传入函数

那么怎么才能点一次按钮就+3呢,很简单,采用传入函数的形式,像这样:

<button onClick={() => {
    setNumber(n => n + 1);
    setNumber(n => n + 1);
    setNumber(n => n + 1);
}}>+3</button>

如果是这样,那么react会:

  • setNumber(n => n + 1):n => n + 1 是一个函数。React 将它加入队列。
  • setNumber(n => n + 1):n => n + 1 是一个函数。React 将它加入队列。
  • setNumber(n => n + 1):n => n + 1 是一个函数。React 将它加入队列。

那么队列中就会:

这样就实现了点击一次+3。

3. 混合

那么假如将两者混起来,也是一样的道理,比如这个例子:

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

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

队列中就是这样的:

所以最终结果是6。

4. 练习

最后看道练习题:

你正在开发一个艺术市场应用,该应用允许一个用户为一个艺术品同时提交多个订单。每次用户按下“购买”按钮,“等待”计数器应该增加一。三秒后,“等待”计数器应该减少,“完成”计数器应该增加。

但是,“等待”计数器的行为并不符合预期。当你按下“购买”按钮时,它会减少到 -1(这本应该是不可能的)。如果你快速点击两次,两个计数器似乎都会出现无法预测的行为。

为什么会发生这种情况?修复两个计数器。
sanbox

很简单,会出现-1是因为如果直接传参,不过点击多少下按钮,当前渲染的state都只会记住初始值0,并在下一个渲染中执行pending - 1,就会产生-1。

将setState改成传入函数形式即可:

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

5. useEffect中的setState

通过函数来setState有时候还有另一个好处,先来看这段代码:

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    connection.on('message', (receivedMessage) => {
      setMessages([...messages, receivedMessage]);
    });
    return () => connection.disconnect();
  }, [roomId, messages]);

connection监听用户发送的信息,每发一条信息就会触发setMessages,看起来没什么问题。

然而,这让messages成了useEffect的依赖,会导致用户每发一条信息,连接就要重启一次。 这无疑是不可接受的用户体验。

解决方案是把setState换成函数传参的方式:

connection.on('message', (receivedMessage) => {
    setMessages(msgs => [...msgs, receivedMessage]);
});

这样一来,useEffect的依赖就不用包含messages了,解决了上面的问题。