组件通常需要根据交互来更改屏幕上的内容。在表单中键入内容应更新输入字段,单击图像轮播上的“下一个”应更改显示的图像,单击“购买”应将产品放入购物车。组件需要“记住”一些内容:当前输入值、当前图像、购物车。在 React 中,这种特定于组件的内存称为 *状态*。
你将学习
- 如何使用
useState
Hook 添加状态变量 useState
Hook 返回哪对值- 如何添加多个状态变量
- 为什么状态被称为局部状态
当普通变量不够用时
这是一个渲染雕塑图像的组件。单击“下一个”按钮应通过将 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 提供了这两件事
- 一个**状态变量**,用于在渲染之间保留数据。
- 一个**状态设置函数**,用于更新变量并触发 React 再次渲染组件。
添加状态变量
要添加状态变量,请在文件顶部从 React 导入 useState
import { useState } from 'react';
然后,替换此行
let index = 0;
为
const [index, setIndex] = useState(0);
index
是一个状态变量,setIndex
是设置函数。
这里的
[
和]
语法称为 数组解构,它允许你从数组中读取值。useState
返回的数组始终只有两个项目。
这就是它们在 handleClick
中的协同方式
function handleClick() {
setIndex(index + 1);
}
现在单击“下一个”按钮将切换当前雕塑
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 into”不同的 React 功能。
状态只是其中一项功能,但你稍后会遇到其他 Hook。
useState
解剖
当您调用 useState
时,您是在告诉 React 您希望此组件记住某些内容
const [index, setIndex] = useState(0);
在这种情况下,您希望 React 记住 index
。
useState
的唯一参数是状态变量的初始值。在此示例中,index
的初始值使用 useState(0)
设置为 0
。
每次组件渲染时,useState
都会为您提供一个包含两个值的数组
- 包含您存储的值的状态变量 (
index
)。 - 可以更新状态变量并触发 React 重新渲染组件的状态设置器函数 (
setIndex
)。
以下是实际操作方式
const [index, setIndex] = useState(0);
- 您的组件第一次渲染。 因为您将
0
作为index
的初始值传递给useState
,所以它将返回[0, setIndex]
。React 会记住0
是最新的状态值。 - 您更新状态。 当用户点击按钮时,它会调用
setIndex(index + 1)
。index
是0
,所以它是setIndex(1)
。这告诉 React 现在记住index
是1
并触发另一次渲染。 - 您的组件第二次渲染。 React 仍然看到
useState(0)
,但因为 React *记住* 您将index
设置为1
,所以它返回[1, setIndex]
。 - 等等!
为组件提供多个状态变量
您可以在一个组件中拥有任意数量和任意类型的状态变量。此组件有两个状态变量,一个数字 index
和一个布尔值 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} /> </> ); }
如果多个状态变量的状态不相关,例如本例中的 index
和 showMore
,则最好使用多个状态变量。但是,如果您发现经常同时更改两个状态变量,则将它们合并为一个可能更容易。例如,如果您有一个包含多个字段的表单,则使用一个包含对象的 state 变量比每个字段都使用 state 变量更方便。阅读选择状态结构了解更多提示。
深入探讨
您可能已经注意到,useState
调用没有接收任何关于它指的是*哪个*状态变量的信息。没有传递给 useState
的“标识符”,那么它如何知道要返回哪个状态变量?它是依靠解析函数之类的魔法吗?答案是不。
相反,为了使其语法简洁,Hook 依赖于在同一组件的每次渲染中都保持稳定的调用顺序。 这在实践中效果很好,因为如果您遵循上述规则(“仅在顶层调用 Hook”),则 Hook 将始终以相同的顺序被调用。此外,linter 插件可以捕获大多数错误。
在内部,React 为每个组件保存一个状态对数组。它还维护当前对索引,该索引在渲染之前设置为 0
。每次调用 useState
时,React 都会为您提供下一个状态对并增加索引。您可以在React Hooks: Not Magic, Just Arrays 中阅读有关此机制的更多信息。
此示例不使用 React,但它可以让您了解 useState
在内部是如何工作的
let componentHooks = []; let currentHookIndex = 0; // How useState works inside React (simplified). function useState(initialState) { let pair = componentHooks[currentHookIndex]; if (pair) { // This is not the first render, // so the state pair already exists. // Return it and prepare for next Hook call. currentHookIndex++; return pair; } // This is the first time we're rendering, // so create a state pair and store it. pair = [initialState, setState]; function setState(nextState) { // When the user requests a state change, // put the new value into the pair. pair[0] = nextState; updateDOM(); } // Store the pair for future renders // and prepare for the next Hook call. componentHooks[currentHookIndex] = pair; currentHookIndex++; return pair; } function Gallery() { // Each useState() call will get the next pair. const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); function handleNextClick() { setIndex(index + 1); } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; // This example doesn't use React, so // return an output object instead of JSX. return { onNextClick: handleNextClick, onMoreClick: handleMoreClick, header: `${sculpture.name} by ${sculpture.artist}`, counter: `${index + 1} of ${sculptureList.length}`, more: `${showMore ? 'Hide' : 'Show'} details`, description: showMore ? sculpture.description : null, imageSrc: sculpture.url, imageAlt: sculpture.alt }; } function updateDOM() { // Reset the current Hook index // before rendering the component. currentHookIndex = 0; let output = Gallery(); // Update the DOM to match the output. // This is the part React does for you. nextButton.onclick = output.onNextClick; header.textContent = output.header; moreButton.onclick = output.onMoreClick; moreButton.textContent = output.more; image.src = output.imageSrc; image.alt = output.imageAlt; if (output.description !== null) { description.textContent = output.description; description.style.display = ''; } else { description.style.display = 'none'; } } let nextButton = document.getElementById('nextButton'); let header = document.getElementById('header'); let moreButton = document.getElementById('moreButton'); let description = document.getElementById('description'); let image = document.getElementById('image'); let sculptureList = [{ name: 'Homenaje a la Neurocirugía', artist: 'Marta Colvin Andrade', description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', url: 'https://i.imgur.com/Mx7dA2Y.jpg', alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' }, { name: 'Floralis Genérica', artist: 'Eduardo Catalano', description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', url: 'https://i.imgur.com/ZF6s192m.jpg', alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' }, { name: 'Eternal Presence', artist: 'John Woodrow Wilson', description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', url: 'https://i.imgur.com/aTtVpES.jpg', alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' }, { name: 'Moai', artist: 'Unknown Artist', description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', url: 'https://i.imgur.com/RCwLEoQm.jpg', alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' }, { name: 'Blue Nana', artist: 'Niki de Saint Phalle', description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', url: 'https://i.imgur.com/Sd1AgUOm.jpg', alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' }, { name: 'Ultimate Form', artist: 'Barbara Hepworth', description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', url: 'https://i.imgur.com/2heNQDcm.jpg', alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' }, { name: 'Cavaliere', artist: 'Lamidi Olonade Fakeye', description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", url: 'https://i.imgur.com/wIdGuZwm.png', alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' }, { name: 'Big Bellies', artist: 'Alina Szapocznikow', description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", url: 'https://i.imgur.com/AlHTAdDm.jpg', alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' }, { name: 'Terracotta Army', artist: 'Unknown Artist', description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', url: 'https://i.imgur.com/HMFmH6m.jpg', alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' }, { name: 'Lunar Landscape', artist: 'Louise Nevelson', description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', url: 'https://i.imgur.com/rN7hY6om.jpg', alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' }, { name: 'Aureole', artist: 'Ranjani Shettar', description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', url: 'https://i.imgur.com/okTpbHhm.jpg', alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' }, { name: 'Hippos', artist: 'Taipei Zoo', description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', url: 'https://i.imgur.com/6o5Vuyu.jpg', alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' }]; // Make UI match the initial state. updateDOM();
即使不理解它,你也可以使用 React,但你可能会发现这是一种有用的心智模型。
状态是隔离且私有的
状态对于屏幕上的组件实例是局部的。换句话说,如果你渲染同一个组件两次,每个副本都将拥有完全隔离的状态!更改其中一个不会影响另一个。
在本例中,之前的 Gallery
组件渲染了两次,其逻辑没有任何变化。尝试单击每个画廊内的按钮。注意,它们的状态是独立的
import Gallery from './Gallery.js'; export default function Page() { return ( <div className="Page"> <Gallery /> <Gallery /> </div> ); }
这就是状态与你在模块顶部声明的常规变量的不同之处。状态不与特定的函数调用或代码中的位置绑定,而是与屏幕上的特定位置“本地”绑定。你渲染了两个 <Gallery />
组件,因此它们的状态是分开存储的。
还要注意 Page
组件如何“不知道”关于 Gallery
状态的任何信息,甚至不知道它是否有状态。与 props 不同,状态对于声明它的组件是完全私有的。 父组件不能更改它。这使你可以向任何组件添加状态或删除状态,而不会影响其他组件。
如果你希望两个画廊都保持状态同步怎么办?在 React 中,正确的方法是从子组件中_删除_状态,并将其添加到它们最近的共享父组件中。接下来的几页将重点介绍如何组织单个组件的状态,但我们将在 在组件之间共享状态 中回到这个主题。
回顾
- 当组件需要在渲染之间“记住”一些信息时,请使用状态变量。
- 状态变量通过调用
useState
Hook 来声明。 - Hooks 是以
use
开头的特殊函数。它们让你可以“钩入”状态等 React 特性。 - Hooks 可能会让你想起导入:它们需要无条件地被调用。调用 Hooks(包括
useState
)只在组件的顶层或另一个 Hook 中有效。 useState
Hook 返回一对值:当前状态和更新它的函数。- 你可以有多个状态变量。在内部,React 会按顺序匹配它们。
- 状态对于组件是私有的。如果你在两个地方渲染它,每个副本都会获得自己的状态。
挑战 1之 4: 完成画廊
当你按下最后一个雕塑上的“下一个”时,代码会崩溃。修复逻辑以防止崩溃。你可以通过向事件处理程序添加额外的逻辑或在操作不可能时禁用按钮来做到这一点。
修复崩溃后,添加一个“上一个”按钮,显示上一个雕塑。它不应该在第一个雕塑上崩溃。
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} /> </> ); }