组件通常需要根据交互更改屏幕上的内容。在表单中键入内容应更新输入字段,点击图片轮播中的“下一步”应更改显示的图片,点击“购买”应将产品放入购物车。组件需要“记住”一些东西:当前输入值、当前图片、购物车。在 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 正在渲染时才可用(我们将在下一页更详细地介绍)。它们允许你“钩入”不同的 React 功能。
状态只是其中一项功能,你稍后会遇到其他 Hook。
useState
的结构
当你调用useState
时,你是在告诉 React 你希望这个组件记住一些东西。
const [index, setIndex] = useState(0);
在本例中,你希望 React 记住index
。
useState
的唯一参数是状态变量的初始值。在这个例子中,index
的初始值使用useState(0)
设置为0
。
每次你的组件渲染时,useState
都会返回一个包含两个值的数组。
- 状态变量(
index
),包含你存储的值。 - 状态设置函数(
setIndex
),它可以更新状态变量并触发 React 再次渲染组件。
以下是它的实际操作方式
const [index, setIndex] = useState(0);
- 你的组件第一次渲染。因为你传递了
0
给useState
作为index
的初始值,它将返回[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
,最好使用多个状态变量。但是,如果你发现经常同时更改两个状态变量,则将它们组合成一个变量可能会更容易。例如,如果你有一个包含许多字段的表单,则使用单个状态变量来保存对象比每个字段使用一个状态变量更方便。阅读选择状态结构以获取更多技巧。
深入探讨
你可能已经注意到,useState
调用没有接收任何关于哪个状态变量的信息。没有“标识符”传递给useState
,那么它如何知道要返回哪个状态变量呢?它是否依赖于某些像解析你的函数这样的魔法?答案是否定的。
相反,为了实现简洁的语法,Hooks依赖于对相同组件的每次渲染都保持稳定的调用顺序。在实践中,这运行良好,因为如果你遵循上面的规则(“只在顶层调用 Hooks”),Hooks 将始终按照相同的顺序被调用。此外,一个代码检查插件可以捕获大多数错误。
在内部,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} /> </> ); }