组件通常需要根据交互更改屏幕上显示的内容。输入表单应该更新输入字段,单击轮播图上的“下一个”应该更改显示的图片,单击“购买”应该将商品放入购物车。组件需要“记住”某些东西:当前输入值、当前图片、购物车。在 React 中,这种组件特有的记忆被称为 state。
你将会学习到
- 如何使用
useState
Hook 添加 state 变量 useState
Hook 返回哪一对值- 如何添加多个 state 变量
- 为什么 state 被称作是局部的
当普通的变量无法满足时
以下是一个渲染雕塑图片的组件。点击 “Next” 按钮应该显示下一个雕塑并将 index
更改为 1
,再次点击又更改为 2
,以此类推。但这个组件现在不起作用(你可以试一试!):
handleClick()
事件处理函数正在更新局部变量 index
。但存在两个原因使得变化不可见:
- 局部变量无法在多次渲染中持久保存。 当 React 再次渲染这个组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
- 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。
要使用新数据更新组件,需要做两件事:
- 保留 渲染之间的数据。
- 触发 React 使用新数据渲染组件(重新渲染)。
useState
Hook 提供了这两个功能:
- State 变量 用于保存渲染间的数据。
- State setter 函数 更新变量并触发 React 再次渲染组件。
添加一个 state 变量
要添加 state 变量,先从文件顶部的 React 中导入 useState
:
import { useState } from 'react';
然后,替换这一行:
let index = 0;
将其修改为
const [index, setIndex] = useState(0);
index
是一个 state 变量,setIndex
是对应的 setter 函数。
这里的
[
和]
语法称为数组解构,它允许你从数组中读取值。useState
返回的数组总是正好有两项。
以下展示了它们在 handleClick()
中是如何共同起作用的:
function handleClick() {
setIndex(index + 1);
}
现在点击 “Next” 按钮切换当前雕塑:
遇见你的第一个 Hook
在 React 中,useState
以及任何其他以“use
”开头的函数都被称为 Hook。
Hook 是特殊的函数,只在 React 渲染时有效(我们将在下一节详细介绍)。它们能让你 “hook” 到不同的 React 特性中去。
State 只是这些特性中的一个,你之后还会遇到其他 Hook。
剖析 useState
当你调用 useState
时,你是在告诉 React 你想让这个组件记住一些东西:
const [index, setIndex] = useState(0);
在这个例子里,你希望 React 记住 index
。
useState
的唯一参数是 state 变量的初始值。在这个例子中,index
的初始值被useState(0)
设置为 0
。
每次你的组件渲染时,useState
都会给你一个包含两个值的数组:
- state 变量 (
index
) 会保存上次渲染的值。 - state setter 函数 (
setIndex
) 可以更新 state 变量并触发 React 重新渲染组件。
以下是实际发生的情况:
const [index, setIndex] = useState(0);
- 组件进行第一次渲染。 因为你将
0
作为index
的初始值传递给useState
,它将返回[0, setIndex]
。 React 记住0
是最新的 state 值。 - 你更新了 state。当用户点击按钮时,它会调用
setIndex(index + 1)
。index
是0
,所以它是setIndex(1)
。这告诉 React 现在记住index
是1
并触发下一次渲染。 - 组件进行第二次渲染。React 仍然看到
useState(0)
,但是因为 React 记住 了你将index
设置为了1
,它将返回[1, setIndex]
。 - 以此类推!
赋予一个组件多个 state 变量
你可以在一个组件中拥有任意多种类型的 state 变量。该组件有两个 state 变量,一个数字 index
和一个布尔值 showMore
,点击 “Show Details” 会改变 showMore
的值:
如果它们不相关,那么存在多个 state 变量是一个好主意,例如本例中的 index
和 showMore
。但是,如果你发现经常同时更改两个 state 变量,那么最好将它们合并为一个。例如,如果你有一个包含多个字段的表单,那么有一个值为对象的 state 变量比每个字段对应一个 state 变量更方便。 选择 state 结构在这方面有更多提示。
深入探讨
你可能已经注意到,useState
在调用时没有任何关于它引用的是哪个 state 变量的信息。没有传递给 useState
的“标识符”,它是如何知道要返回哪个 state 变量呢?它是否依赖于解析函数之类的魔法?答案是否定的。
相反,为了使语法更简洁,在同一组件的每次渲染中,Hooks 都依托于一个稳定的调用顺序。这在实践中很有效,因为如果你遵循上面的规则(“只在顶层调用 Hooks”),Hooks 将始终以相同的顺序被调用。此外,linter 插件也可以捕获大多数错误。
在 React 内部,为每个组件保存了一个数组,其中每一项都是一个 state 对。它维护当前 state 对的索引值,在渲染之前将其设置为 “0”。每次调用 useState 时,React 都会为你提供一个 state 对并增加索引值。你可以在文章 React Hooks: not magic, just arrays中阅读有关此机制的更多信息。
这个例子没有使用 React,但它让你了解 useState
在内部是如何工作的:
你不必理解它就可以使用 React,但你可能会发现这是一个有用的心智模型。
State 是隔离且私有的
State 是屏幕上组件实例内部的状态。换句话说,如果你渲染同一个组件两次,每个副本都会有完全隔离的 state!改变其中一个不会影响另一个。
在这个例子中,之前的 Gallery
组件以同样的逻辑被渲染了两次。试着点击每个画廊内的按钮。你会注意到它们的 state 是相互独立的:
这就是 state 与声明在模块顶部的普通变量不同的原因。 State 不依赖于特定的函数调用或在代码中的位置,它的作用域“只限于”屏幕上的某块特定区域。你渲染了两个 <Gallery />
组件,所以它们的 state 是分别存储的。
还要注意 Page
组件“不知道”关于 Gallery
state 的任何信息,甚至不知道它是否有任何 state。与 props 不同,state 完全私有于声明它的组件。父组件无法更改它。这使你可以向任何组件添加或删除 state,而不会影响其他组件。
如果你希望两个画廊保持其 states 同步怎么办?在 React 中执行此操作的正确方法是从子组件中删除 state 并将其添加到离它们最近的共享父组件中。接下来的几节将专注于组织单个组件的 state,但我们将在组件间共享 state 中回到这个主题。
摘要
- 当一个组件需要在多次渲染间“记住”某些信息时使用 state 变量。
- State 变量是通过调用
useState
Hook 来声明的。 - Hook 是以
use
开头的特殊函数。它们能让你 “hook” 到像 state 这样的 React 特性中。 - Hook 可能会让你想起 import:它们需要在非条件语句中调用。调用 Hook 时,包括
useState
,仅在组件或另一个 Hook 的顶层被调用才有效。 useState
Hook 返回一对值:当前 state 和更新它的函数。- 你可以拥有多个 state 变量。在内部,React 按顺序匹配它们。
- State 是组件私有的。如果你在两个地方渲染它,则每个副本都有独属于自己的 state。
第 1 个挑战 共 4 个挑战: 完成画廊组件
当你在最后一个雕塑上按 “Next” 时,代码会发生崩溃。请修复逻辑以防止此崩溃。你可以尝试在事件处理函数中添加额外的逻辑,或在操作无法执行时禁用掉按钮。
修复崩溃后,添加一个显示上一个雕塑的 “Previous” 按钮。同样地,确保它不在第一个雕塑里发生崩溃。