v19.2Latest

State:组件的记忆

组件通常需要根据交互来改变屏幕上显示的内容。在表单中输入应该更新输入框,点击图片轮播的“下一张”应该改变显示的图片,点击“购买”应该将商品放入购物车。组件需要“记住”一些东西:当前的输入值、当前的图片、购物车。在 React 中,这种特定于组件的记忆被称为状态

您将学习
  • 如何使用 useStateHook 添加状态变量
  • useState Hook 返回哪一对值
  • 如何添加多个状态变量
  • 为什么状态被称为局部的

当普通变量不够用时

这是一个渲染雕塑图片的组件。点击“Next”按钮应该通过将 index改为1,然后是2,依此类推,来显示下一座雕塑。然而,这不会生效(你可以试试!):

事件处理函数handleClick 正在更新一个局部变量 index。但有两件事阻止了这种变化被看到:

  1. 局部变量在渲染之间无法持久存在。当 React 第二次渲染这个组件时,它会从头开始渲染——它不会考虑局部变量的任何变化。
  2. 更改局部变量不会触发渲染。React 没有意识到它需要用新数据再次渲染组件。

要用新数据更新组件,需要发生两件事:

  1. 保留渲染之间的数据。
  2. 触发React使用新数据渲染组件(重新渲染)。

useStateHook 提供了这两样东西:

  1. 一个状态变量,用于在渲染之间保留数据。
  2. 一个状态设置函数,用于更新该变量并触发React再次渲染组件。

添加一个状态变量

要添加一个状态变量,请在文件顶部从React导入useState

然后,替换这一行:

index是一个状态变量,setIndex 是设置函数。

这里的[] 语法称为 数组解构,它允许你从数组中读取值。useState返回的数组总是恰好有两项。

它们在 handleClick中是这样协同工作的:

现在点击“Next”按钮会切换当前雕塑:

认识你的第一个 Hook

在 React 中,useState以及任何其他以“use”开头的函数都被称为 Hook。

Hook是一种特殊的函数,仅在 React 渲染时可用(我们将在下一页详细讨论)。它们让你可以“钩入”不同的 React 功能。

状态只是这些功能之一,稍后你将遇到其他 Hook。

陷阱

Hook——以use开头的函数——只能在你的组件或你自己的 Hook的顶层调用。你不能在条件、循环或其他嵌套函数内部调用 Hook。Hook 是函数,但将它们视为关于组件需求的无条件声明会更有帮助。你在组件顶部“使用” React 功能,类似于在文件顶部“导入”模块。

剖析 useState

当你调用 useState时,你是在告诉 React 你希望这个组件记住某些东西:

在这个例子中,你希望 React 记住index

注意

约定是将这对值命名为类似 const [something, setSomething]的形式。你可以随意命名,但约定有助于跨项目理解。

传递给 useState的唯一参数是你的状态变量的初始值。在这个例子中,index的初始值通过 0useState(0)设置为 0。

每次你的组件渲染时,useState都会给你一个包含两个值的数组:

  1. 存储了值的状态变量(index)。
  2. 用于更新该状态变量的状态设置函数(setIndex),它可以更新状态变量并触发 React 再次渲染组件。

以下是其实际运作过程:

  1. 你的组件进行首次渲染。 因为你向 useState传递了0 作为 index的初始值,所以它将返回[0, setIndex]。React 会记住0 是最新的状态值。
  2. 你更新了状态。当用户点击按钮时,它会调用setIndex(index + 1)index0,所以就是setIndex(1)。这告诉 React 现在记住index1 并触发另一次渲染。
  3. 你的组件进行第二次渲染。React 仍然看到useState(0),但因为 React记住 了你将 index设置为了1,所以它将返回[1, setIndex]
  4. 以此类推!

为一个组件声明多个状态变量

你可以在一个组件中拥有任意数量的、任意类型的 state 变量。这个组件有两个 state 变量:一个数字index和一个布尔值showMore,点击“显示详情”时会切换后者:

如果多个状态变量之间没有关联,例如本例中的 indexshowMore,那么使用多个状态变量是个好主意。但如果你发现经常需要同时修改两个状态变量,将它们合并为一个可能会更方便。例如,如果一个表单包含多个字段,使用一个持有对象的状态变量要比为每个字段单独设置状态变量更方便。更多技巧请阅读选择状态结构

状态是隔离且私有的

状态是屏幕上组件实例的本地状态。换句话说,如果你渲染同一个组件两次,每个副本都将拥有完全隔离的状态!更改其中一个的状态不会影响另一个。

在这个例子中,前面的Gallery 组件被

这就是状态与你在模块顶部声明的普通变量的区别。状态并不与特定的函数调用或代码中的某个位置绑定,而是“局部”于屏幕上特定的位置。你渲染了两个<Gallery />组件,因此它们的状态是分开存储的。

还要注意Page组件如何完全“不知道”Gallery的状态,甚至不知道它是否有状态。与 props 不同,状态对于声明它的组件是完全私有的。父组件无法更改它。这让你可以向任何组件添加状态或从中移除状态,而不会影响其他组件。

如果你想让两个画廊的状态保持同步该怎么办?在 React 中正确的做法是移除子组件中的状态,并将其添加到它们最近的共享父组件中。接下来的几页将重点讨论单个组件的状态组织,但我们会在在组件间共享状态中回到这个话题。

回顾

  • 当组件需要在多次渲染之间“记住”某些信息时,使用状态变量。
  • 状态变量通过调用 useStateHook 来声明。
  • Hook 是以 use开头的特殊函数。它们让你可以“接入”像状态这样的 React 功能。
  • Hook 可能会让你联想到导入:它们需要被无条件调用。调用 Hook(包括useState)仅在组件或另一个 Hook 的顶层有效。
  • useState Hook 返回一对值:当前状态和更新它的函数。
  • 你可以拥有多个状态变量。在内部,React 根据它们的顺序进行匹配。
  • 状态是组件的私有属性。如果你在两个地方渲染它,每个副本都有自己的状态。

Try out some challenges

When you press “Next” on the last sculpture, the code crashes. Fix the logic to prevent the crash. You may do this by adding extra logic to event handler or by disabling the button when the action is not possible.

After fixing the crash, add a “Previous” button that shows the previous sculpture. It shouldn’t crash on the first sculpture.