建立 React 函数式组件的心智模型

Apr 21, 2024

先来看看这样一个场景。

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(count + 1)
    console.log(count)
  }

  return (
    <div>
      <button onClick={handleClick}>{count}</button>
    </div>
  )
}

按钮最开始显示为 0

第一次点击之后显示为 1

第二次点击之后显示为 2

......

看起来,一切都跟预期设想的一样。但是打开控制台,会发现在第一次点击之后,按钮显示为 1 而控制台输出的是 0。是代码哪里写错了吗?明明把count的状态修改为 1,为什么控制台还是输出 0 呢?

其实代码并没有什么问题,而是一开始我们还没有认清 React 的渲染机制。通俗来讲,React 属于状态驱动渲染。只要状态改变,就会触发渲染;而新的渲染,又会携带新的状态。

重新看回上面的代码,不过这次换一种方式,我们把count换成数值。(并非真的改成数值,只是一种便于理解的视角)

// 初次渲染时count 为 0
export default function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    setCount(0 + 1) // count 将会被修改为 1
    console.log(0)
  }

  return (
    <div>
      <button onClick={handleClick}>{0}</button>
    </div>
  )
}

第一次点击按钮后,会修改count的状态,并触发第一次重新渲染。

// 第一次重新渲染时count 为 1
export default function Counter() {
  const [count, setCount] = useState(1)

  function handleClick() {
    setCount(1 + 1) // count 将会被修改为 2
    console.log(1)
  }

  return (
    <div>
      <button onClick={handleClick}>{1}</button>
    </div>
  )
}

同理,第二次点击按钮之后...

// 第二次重新渲染时count 为 2
export default function Counter() {
  const [count, setCount] = useState(2)

  function handleClick() {
    setCount(2 + 1) // count 将会被修改为 3
    console.log(2)
  }

  return (
    <div>
      <button onClick={handleClick}>{2}</button>
    </div>
  )
}

为什么能把 count 换成上面的数值呢?这是因为状态是不可变的,它其实就是一个常量,组件的每一次渲染都有属于它们自己的状态。也就是说,在函数式组件在完成渲染返回 JSX 之前,count会一直保持原来的数值。只有在重新渲染时,函数式组件才会以新的状态来渲染视图。

再来看看下面的例子。

setCount(count += 1) // 编辑器会报错

改成这种写法之后,编辑器会告诉你'count' is constant。我们并不能修改count的数值,只能够给setter函数传入新的数值。这也很好地印证了我们上面的看法。

所以,请记住“state 如同一张快照”。setter函数不会修改已有的state,只是触发重新渲染,而新的渲染会给你带来新的state