State:组件的状态
组件通常需要根据交互更改屏幕上显示的内容。输入表单应该更新输入字段,单击轮播图上的“下一个”应该更改显示的图片,单击“购买”应该将商品放入购物车。组件需要“记住”某些东西:当前输入值、当前图片、购物车。在 React 中,这种组件特有的记忆被称为 state。
当普通的变量无法满足时
以下是一个渲染雕塑图片的组件。点击 “Next” 按钮应该显示下一个雕塑并将 index
更改为 1
,再次点击又更改为 2
,以此类推。但这个组件现在不起作用:
import { sculptureList } from './data.js';
export default function Gallery() {
let index = 0;
function handleClick() {
index = index + 1;
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<img src={sculpture.url} alt={sculpture.alt}/>
<p>
{sculpture.description}
</p>
</>
);
}
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” 按钮切换当前雕塑:
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
function handleClick() {
setIndex(index + 1);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<img src={sculpture.url} alt={sculpture.alt}/>
<p>
{sculpture.description}
</p>
</>
);
}
Hook
在 React 中,useState
以及任何其他以“use
”开头的函数都被称为 Hook。
Hook 是特殊的函数,只在 React 渲染时有效。它们能让你 “hook” 到不同的 React 特性中去。
State 只是这些特性中的一个,之后还会遇到其他 Hook。
注意:Hooks ——以 use
开头的函数——只能在组件或自定义 Hook 的最顶层调用。 不能在条件语句、循环语句或其他嵌套函数内调用 Hook。Hook 是函数,但将它们视为关于组件需求的无条件声明会很有帮助。在组件顶部 “use” React 特性,类似于在文件顶部“导入”模块。
剖析 useState
当调用 useState
时,是在告诉 React 想让这个组件记住一些东西:
const [index, setIndex] = useState(0);
在这个例子里,希望 React 记住 index
。
注意:惯例是将这对返回值命名为 const [thing, setThing]
。也可以将其命名为任何喜欢的名称,但遵照约定俗成能使跨项目合作更易理解。
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
的值:
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>{sculpture.description}</p>}
<img src={sculpture.url} alt={sculpture.alt}/>
</>
);
}
如果它们不相关,那么存在多个 state 变量是一个好主意,例如本例中的 index
和 showMore
。但是,如果发现经常同时更改两个 state 变量,那么最好将它们合并为一个。例如,如果有一个包含多个字段的表单,那么有一个值为对象的 state 变量比每个字段对应一个 state 变量更方便。 选择 state 结构在这方面有更多提示。
State 是隔离且私有的
State 是屏幕上组件实例内部的状态。换句话说,如果渲染同一个组件两次,每个副本都会有完全隔离的 state!改变其中一个不会影响另一个。
在这个例子中,之前的 Gallery
组件以同样的逻辑被渲染了两次。试着点击每个画廊内的按钮。会注意到它们的 state 是相互独立的:
import Gallery from './Gallery.js';
export default function Page() {
return (
<div className="Page">
<Gallery />
<Gallery />
</div>
);
}
这就是 state 与声明在模块顶部的普通变量不同的原因。 State 不依赖于特定的函数调用或在代码中的位置,它的作用域“只限于”屏幕上的某块特定区域。渲染了两个 <Gallery />
组件,所以它们的 state 是分别存储的。
还要注意 Page
组件“不知道”关于 Gallery
state 的任何信息,甚至不知道它是否有任何 state。与 props 不同,state 完全私有于声明它的组件。父组件无法更改它。这使你可以向任何组件添加或删除 state,而不会影响其他组件。
如果希望两个画廊保持其 states 同步怎么办?在 React 中执行此操作的正确方法是从子组件中删除 state 并将其添加到离它们最近的共享父组件中。也叫做状态提升。
摘要
- 当一个组件需要在多次渲染间“记住”某些信息时使用 state 变量。
- State 变量是通过调用
useState
Hook 来声明的。 - Hook 是以
use
开头的特殊函数。它们能让你 “hook” 到像 state 这样的 React 特性中。 - Hook 可能会让你想起 import:它们需要在非条件语句中调用。调用 Hook 时,包括
useState
,仅在组件或另一个 Hook 的顶层被调用才有效。 useState
Hook 返回一对值:当前 state 和更新它的函数。- 可以拥有多个 state 变量。在内部,React 按顺序匹配它们。
- State 是组件私有的。如果在两个地方渲染它,则每个副本都有独属于自己的 state。