组件经常需要根据交互来更改屏幕上的内容。输入表单时应该更新输入字段,点击图片轮播的“下一张”应更改显示的图片,点击“购买”应将产品放入购物车。组件需要“记住”一些东西:当前的输入值、当前的图片、购物车。在 React 中,这种组件特有的记忆被称为状态。
🌐 Components often need to change what’s on the screen as a result of an interaction. Typing into the form should update the input field, clicking “next” on an image carousel should change which image is displayed, clicking “buy” should put a product in the shopping cart. Components need to “remember” things: the current input value, the current image, the shopping cart. In React, this kind of component-specific memory is called state.
你将学习到
- 如何使用
useStateHook 添加状态变量 useState钩子返回的值对是什么- 如何添加多个状态变量
- 为什么状态在局部被调用
当常规变量不够时
🌐 When a regular variable isn’t enough
这是一个渲染雕塑图片的组件。点击“下一步”按钮应该显示下一个雕塑,通过将 index 改为 1,然后是 2,依此类推。然而,这不起作用(你可以试试!):
🌐 Here’s a component that renders a sculpture image. Clicking the “Next” button should show the next sculpture by changing the index to 1, then 2, and so on. However, this won’t work (you can try it!):
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。但是有两件事会导致该更改不可见:
🌐 The handleClick event handler is updating a local variable, index. But two things prevent that change from being visible:
- 局部变量在渲染之间不会持续存在。 当 React 第二次渲染这个组件时,它会从头开始渲染——不会考虑局部变量的任何变化。
- 对局部变量的更改不会触发渲染。 React 无法意识到它需要使用新数据重新渲染组件。
要使用新数据更新组件,需要发生两件事:
🌐 To update a component with new data, two things need to happen:
- 在渲染之间保留 数据。
- 触发 React 用新数据渲染组件(重新渲染)。
useState 钩子提供这两样东西:
🌐 The useState Hook provides those two things:
- 一个 状态变量 用于在渲染之间保留数据。
- 一个状态设置函数用来更新变量并触发 React 重新渲染组件。
添加状态变量
🌐 Adding a state variable
要添加一个状态变量,在文件顶部从 React 导入 useState:
🌐 To add a state variable, import useState from React at the top of the file:
import { useState } from 'react';然后,替换这一行:
🌐 Then, replace this line:
let index = 0;with
const [index, setIndex] = useState(0);index 是一个状态变量,setIndex 是设置函数。
这里的
[和]语法称为数组解构,它允许你从数组中读取值。useState返回的数组总是恰好有两个项目。
这就是它们在 handleClick 中协同工作的方式:
🌐 This is how they work together in handleClick:
function handleClick() {
setIndex(index + 1);
}现在点击“下一步”按钮会切换当前的雕塑:
🌐 Now clicking the “Next” button switches the current sculpture:
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> </> ); }
认识你的第一个钩子
🌐 Meet your first Hook
在 React 中,useState,以及任何以 use 开头的函数,都被称为 Hook。
🌐 In React, useState, as well as any other function starting with “use”, is called a Hook.
Hooks 是一些特殊的函数,它们只在 React 渲染 时可用(我们将在下一页详细介绍)。它们让你可以“接入”不同的 React 功能。
状态只是其中的一个特性,但你稍后会遇到其他钩子。
🌐 State is just one of those features, but you will meet the other Hooks later.
useState 的剖析学
🌐 Anatomy of useState
当你调用 useState 时,你是在告诉 React 你希望这个组件记住某些东西:
🌐 When you call useState, you are telling React that you want this component to remember something:
const [index, setIndex] = useState(0);在这种情况下,你希望 React 记住 index。
🌐 In this case, you want React to remember index.
useState 的唯一参数是你的状态变量的初始值。在这个例子中,index 的初始值通过 useState(0) 设置为 0。
🌐 The only argument to useState is the initial value of your state variable. In this example, the index’s initial value is set to 0 with useState(0).
每次你的组件渲染时,useState 会给你一个包含两个值的数组:
🌐 Every time your component renders, useState gives you an array containing two values:
- 你存储的值对应的状态变量(
index)。 - 状态设置函数(
setIndex),可以更新状态变量并触发 React 重新渲染组件。
这是实际发生的情况:
🌐 Here’s how that happens in action:
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]。 - 以此类推!
为组件提供多个状态变量
🌐 Giving a component multiple state variables
在一个组件中,你可以拥有任意数量和类型的状态变量。这个组件有两个状态变量,一个数字 index 和一个布尔值 showMore,当你点击“显示详细信息”时会切换其值:
🌐 You can have as many state variables of as many types as you like in one component. This component has two state variables, a number index and a boolean showMore that’s toggled when you click “Show details”:
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,拥有多个状态变量是个好主意。但是,如果你发现自己经常一起更改两个状态变量,合并成一个可能会更容易。例如,如果你有一个包含很多字段的表单,使用一个保存对象的状态变量比为每个字段设置一个状态变量更方便。更多提示请阅读 选择状态结构。
🌐 It is a good idea to have multiple state variables if their state is unrelated, like index and showMore in this example. But if you find that you often change two state variables together, it might be easier to combine them into one. For example, if you have a form with many fields, it’s more convenient to have a single state variable that holds an object than state variable per field. Read Choosing the State Structure for more tips.
深入研究
🌐 How does React know which state to return?
你可能已经注意到,useState 调用没有接收到关于它指向的 哪个 状态变量的任何信息。没有任何“标识符”被传递给 useState,那么它如何知道返回哪个状态变量呢?它会依赖某种像解析你的函数这样的魔法吗?答案是否定的。
🌐 You might have noticed that the useState call does not receive any information about which state variable it refers to. There is no “identifier” that is passed to useState, so how does it know which of the state variables to return? Does it rely on some magic like parsing your functions? The answer is no.
相反,为了实现它们简洁的语法,Hooks 依赖于同一组件每次渲染时的稳定调用顺序。 实践中这运行良好,因为如果你遵循上述规则(“只在顶层调用 Hooks”),Hooks 将始终按相同顺序被调用。此外,linter 插件 可以捕捉大多数错误。
🌐 Instead, to enable their concise syntax, Hooks rely on a stable call order on every render of the same component. This works well in practice because if you follow the rule above (“only call Hooks at the top level”), Hooks will always be called in the same order. Additionally, a linter plugin catches most mistakes.
在内部,React 为每个组件保存一组状态对的数组。它还维护当前的状态对索引,该索引在渲染之前被设置为 0。每次你调用 useState 时,React 会给你下一个状态对并递增索引。你可以在 React Hooks: Not Magic, Just Arrays. 中阅读更多关于此机制的信息。
🌐 Internally, React holds an array of state pairs for every component. It also maintains the current pair index, which is set to 0 before rendering. Each time you call useState, React gives you the next state pair and increments the index. You can read more about this mechanism in React Hooks: Not Magic, Just Arrays.
这个例子不使用 React,但它可以让你了解 useState 的内部工作原理:
🌐 This example doesn’t use React but it gives you an idea of how useState works internally:
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,但你可能会发现这是一个有用的心智模型。
🌐 You don’t have to understand it to use React, but you might find this a helpful mental model.
状态是隔离的和私有的
🌐 State is isolated and private
状态是屏幕上组件实例本地的。换句话说,如果你渲染同一个组件两次,每个副本的状态都是完全独立的! 改变其中一个不会影响另一个。
🌐 State is local to a component instance on the screen. In other words, if you render the same component twice, each copy will have completely isolated state! Changing one of them will not affect the other.
在这个示例中,之前的 Gallery 组件被渲染了两次,其逻辑没有任何改变。尝试点击每个图库中的按钮。注意它们的状态是独立的:
🌐 In this example, the Gallery component from earlier is rendered twice with no changes to its logic. Try clicking the buttons inside each of the galleries. Notice that their state is independent:
import Gallery from './Gallery.js'; export default function Page() { return ( <div className="Page"> <Gallery /> <Gallery /> </div> ); }
这就是让状态不同于你可能在模块顶部声明的常规变量的原因。状态不与特定的函数调用或代码位置绑定,而是对屏幕上的特定位置“本地化”。你渲染了两个 <Gallery /> 组件,因此它们的状态是分别存储的。
🌐 This is what makes state different from regular variables that you might declare at the top of your module. State is not tied to a particular function call or a place in the code, but it’s “local” to the specific place on the screen. You rendered two <Gallery /> components, so their state is stored separately.
还要注意 Page 组件并不知道 Gallery 的状态,甚至不知道它是否有状态。与 props 不同,state 对声明它的组件是完全私有的。 父组件无法改变它。这让你可以在任何组件中添加状态或移除状态,而不会影响其他组件。
🌐 Also notice how the Page component doesn’t “know” anything about the Gallery state or even whether it has any. Unlike props, state is fully private to the component declaring it. The parent component can’t change it. This lets you add state to any component or remove it without impacting the rest of the components.
如果你希望两个图库都保持其状态同步怎么办?在 React 中的正确做法是从子组件中移除状态,并将其添加到它们最近的共享父组件中。接下来的几页将重点讲解单个组件的状态组织,但我们将在组件之间共享状态中回到这个话题。
🌐 What if you wanted both galleries to keep their states in sync? The right way to do it in React is to remove state from child components and add it to their closest shared parent. The next few pages will focus on organizing state of a single component, but we will return to this topic in Sharing State Between Components.
回顾
- 当组件需要在多次渲染之间“记住”一些信息时,使用状态变量。
- 状态变量通过调用
useStateHook 声明。 - Hooks 是以
use开头的特殊函数。它们让你“钩入” React 的功能,比如状态。 - Hooks 可能会让你想起导入:它们需要无条件调用。调用 Hooks,包括
useState,仅在组件的顶层或另一个 Hook 中才有效。 useStateHook 返回一对值:当前状态和更新它的函数。- 你可以拥有多个状态变量。在内部,React 会根据它们的顺序进行匹配。
- 状态是组件私有的。如果你在两个地方渲染它,每个副本都会有自己的状态。
挑战 1 of 4: 完成图库
🌐 Complete the gallery
当你在最后一件雕塑上按“下一步”时,代码会崩溃。修复逻辑以防止崩溃。你可以通过在事件处理程序中添加额外的逻辑,或者在操作不可行时禁用按钮来实现这一点。
🌐 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.
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} /> </> ); }