3 Mistakes Junior Developers Make with React Function Component State

2025-05-28

初级开发人员在使用 React 函数组件状态时常犯的 3 个错误

几周前,我写了一篇文章,专门讨论开发人员在使用 React 组件状态时常犯的错误。我提供的所有示例都使用了类组件和setState方法。

我曾多次被问到这些原则是否适用于函数组件和钩子。答案是肯定的!

应广大用户的要求,本文将探讨同样的概念,但这次我们将使用useState钩子来处理函数组件。我们将探讨三个常见的错误以及如何纠正它们。


1. 直接修改状态

更改组件状态时,务必返回包含修改内容的新状态副本,而不是直接修改当前状态。如果您错误地修改了组件的状态,React 的 diffing 算法将无法捕获该更改,从而导致组件无法正确更新。

我们来看一个例子。假设你有一个如下所示的状态:

const initialState = ['red', 'blue', 'green']
let [colors] = useState(initialState)
Enter fullscreen mode Exit fullscreen mode

现在你想将颜色“黄色”添加到这个数组中。你可能想这样做:

colors.push('yellow')
Enter fullscreen mode Exit fullscreen mode

甚至这样:

colors = [...colors, 'yellow']
Enter fullscreen mode Exit fullscreen mode

但这两种方法都是错误的!在函数组件中更新状态时,你始终需要使用useState钩子提供的 setter 方法,并且务必注意不要修改对象。setter 方法是useState返回数组中的第二个元素,因此你可以像解构状态值一样对其进行解构。

以下是将元素添加到数组的正确方法:

// Initial setup
const initialState = ['red', 'blue', 'green']
const [colors, setColors] = useState(initialState)

// Later, modifying the state
setColors(colors => [...colors, 'yellow'])
Enter fullscreen mode Exit fullscreen mode

这直接导致了我们的第二个错误。


2. 不使用函数来设置依赖于先前状态的状态

钩子返回的 setter 方法有两种使用方式useState。第一种是提供一个新值作为参数。第二种是提供一个函数作为参数。那么,什么时候应该使用其中一种呢?

例如,如果你有一个可以启用或禁用的按钮,你可能会有一个叫做 的状态,isDisabled它保存一个布尔值。如果你想将按钮从启用状态切换到禁用状态,你可能会想写这样的东西,使用一个值作为参数:

// Initial setup
const [isDisabled, setIsDisabled] = useState(false)

// Later, modifying the state
setIsDisabled(!isDisabled)
Enter fullscreen mode Exit fullscreen mode

那么,这有什么问题呢?问题在于 React 状态更新可以批量处理,这意味着单个更新周期内可能会发生多个状态更新。如果您的更新是批量处理的,并且启用/禁用状态有多个更新,最终结果可能与您预期的不一样。

这里更新状态的更好方法是提供先前状态的函数作为参数:

// Initial setup
const [isDisabled, setIsDisabled] = useState(false)

// Later, modifying the state
setIsDisabled(isDisabled => !isDisabled)
Enter fullscreen mode Exit fullscreen mode

现在,即使您的状态更新是分批的,并且对启用/禁用状态的多次更新是一起进行的,每次更新都将依赖于正确的先前状态,以便您始终能够获得期望的结果。

对于诸如增加计数器之类的操作来说也是如此。

不要这样做:

// Initial setup
const [counterValue, setCounterValue] = useState(0)

// Later, modifying the state
setCounterValue(counterValue + 1)
Enter fullscreen mode Exit fullscreen mode

执行以下操作:

// Initial setup
const [counterValue, setCounterValue] = useState(0)

// Later, modifying the state
setCounterValue(counterValue => counterValue + 1)
Enter fullscreen mode Exit fullscreen mode

这里的关键是,如果新状态依赖于旧状态的值,则应始终使用函数作为参数。如果要设置不依赖于旧状态的值,则可以使用值作为参数。


3. 忘记了 setter 方法useState是异步的

最后,需要记住的是,钩子返回的 setter 方法useState是异步方法。例如,假设我们有一个状态如下的组件:

const [name, setName] = useState('John')
Enter fullscreen mode Exit fullscreen mode

然后我们有一个方法来更新状态,然后将状态记录到控制台:

const setNameToMatt = () => {
  setName('Matt')
  console.log(`The name is now... ${name}!`)
}
Enter fullscreen mode Exit fullscreen mode

你可能以为这会打印'Matt'到控制台,但事实并非如此!它打印了'John'

原因在于,钩子返回的 setter 方法useState是异步的。这意味着当它到达调用 的那一行时,它会启动状态更新setName,但由于异步代码是非阻塞的,因此它下面的代码将继续执行。

如果您有需要在状态更新后运行的代码,React 提供了useEffect钩子,它允许您编写在任何指定的依赖项更新后运行的代码。

setState(这与使用向类组件中的方法提供的回调函数的方式有点不同。无论出于何种原因,useState钩子不支持相同的 API,因此回调函数在这里不起作用。)

更新后记录当前状态的正确方法是:

useEffect(() => {
  if (name !== 'John') {
    console.log(`The name is now... ${name}!`)
  }
}, [name])

const setNameToMatt = () => setName('Matt')
Enter fullscreen mode Exit fullscreen mode

好多了!现在它能'Matt'按预期正确记录了。

(请注意,在这种情况下,我在此处添加了if语句以防止在组件首次挂载时发生控制台日志。如果您想要一个更通用的解决方案,建议使用 useRef 钩子来保存在组件挂载后更新的值,这将成功阻止您的useEffect钩子在组件首次挂载时运行。)


结论

好了!三个常见错误以及如何改正。记住,犯错是可以的。你在学习。我在学习。我们都在学习。让我们继续学习,一起进步。

如果您想查看此处使用的示例(以及更多内容)的一些现场演示,请访问http://tylerhawkins.info/react-component-state-demo/build/

您还可以在 GitHub 上找到代码

文章来源:https://dev.to/thawkin3/3-mistakes-junior-developers-make-with-react-function-component-state-88a
PREV
招聘高级软件工程师时我需要注意什么
NEXT
30+最佳免费单页网站模板