状态:组件的内存

作为交互的结果,组件通常需要更改屏幕上的内容。在表单中输入应该更新输入字段,单击图片轮播上的 “下一个” 应该更改显示的图片,单击 “购买” 应该将产品放入购物车。组件需要 “记住” 的东西:当前输入值,当前图片,购物车。在 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.

你将学习到

  • 如何使用 useState 钩子添加状态变量

    ¥How to add a state variable with the useState Hook

  • useState 钩子返回哪对值

    ¥What pair of values the useState Hook returns

  • 如何添加多个状态变量

    ¥How to add more than one state variable

  • 为什么状态在局部被调用

    ¥Why state is called local

当常规变量不够时

¥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:

  1. 局部变量不会在渲染之间持续存在。当 React 第二次渲染此组件时,它会从头开始渲染它 - 它不会考虑对局部变量的任何更改。

    ¥Local variables don’t persist between renders. When React renders this component a second time, it renders it from scratch—it doesn’t consider any changes to the local variables.

  2. 对局部变量的更改不会触发渲染。React 没有意识到它需要用新数据再次渲染组件。

    ¥Changes to local variables won’t trigger renders. React doesn’t realize it needs to render the component again with the new data.

要使用新数据更新组件,需要发生两件事:

¥To update a component with new data, two things need to happen:

  1. 保留渲染之间的数据。

    ¥Retain the data between renders.

  2. 触发 React 以使用新数据渲染组件(重新渲染)。

    ¥Trigger React to render the component with new data (re-rendering).

useState 钩子提供了这两件事:

¥The useState Hook provides those two things:

  1. 用于保留渲染之间数据的状态变量。

    ¥A state variable to retain the data between renders.

  2. 一个状态设置函数,用于更新变量并触发 React 再次渲染组件。

    ¥A state setter function to update the variable and trigger React to render the component again.

添加状态变量

¥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 是设置函数。

¥index is a state variable and setIndex is the setter function.

这里的 [] 语法称为 数组解构,它允许你从数组中读取值。useState 返回的数组始终恰好有两项。

¥The [ and ] syntax here is called array destructuring and it lets you read values from an array. The array returned by useState always has exactly two items.

这就是它们在 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.

钩子是特殊函数,只有在 React 是 渲染 时才可用(我们将在下一页详细介绍)。它们让你拥有 “接入” 种不同的 React 特性。

¥Hooks are special functions that are only available while React is rendering (which we’ll get into in more detail on the next page). They let you “hook into” different React features.

状态只是其中的一个特性,但你稍后会遇到其他钩子。

¥State is just one of those features, but you will meet the other Hooks later.

易犯错误

Hooks(以 use 开头的函数)只能在组件的顶层调用,或者 你自己的钩子 你不能在条件、循环或其他嵌套函数内调用钩子。钩子是函数,但将它们视为关于组件需求的无条件声明会很有帮助。你在组件顶部的 “use” React 特性类似于你在文件顶部的 “导入” 模块。

¥Hooks—functions starting with use—can only be called at the top level of your components or your own Hooks. You can’t call Hooks inside conditions, loops, or other nested functions. Hooks are functions, but it’s helpful to think of them as unconditional declarations about your component’s needs. You “use” React features at the top of your component similar to how you “import” modules at the top of your file.

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.

注意

惯例是将这对命名为 const [something, setSomething]。你可以随意命名,但约定会让项目之间的事情更容易理解。

¥The convention is to name this pair like const [something, setSomething]. You could name it anything you like, but conventions make things easier to understand across projects.

useState 的唯一参数是状态变量的初始值。在本例中,index 的初始值设置为 0useState(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:

  1. 状态变量 (index) 以及你存储的值。

    ¥The state variable (index) with the value you stored.

  2. 状态设置函数(setIndex),可以更新状态变量并触发 React 再次渲染组件。

    ¥The state setter function (setIndex) which can update the state variable and trigger React to render the component again.

这是实际发生的情况:

¥Here’s how that happens in action:

const [index, setIndex] = useState(0);
  1. 你的组件第一次渲染。因为你将 0 传递给 useState 作为 index 的初始值,它会返回 [0, setIndex]。React 记住 0 是最新的状态值。

    ¥Your component renders the first time. Because you passed 0 to useState as the initial value for index, it will return [0, setIndex]. React remembers 0 is the latest state value.

  2. 你更新状态。当用户单击该按钮时,它会调用 setIndex(index + 1)index0,所以它是 setIndex(1)。这告诉 React 记住 index 现在是 1 并触发另一个渲染。

    ¥You update the state. When a user clicks the button, it calls setIndex(index + 1). index is 0, so it’s setIndex(1). This tells React to remember index is 1 now and triggers another render.

  3. 你的组件的第二次渲染。React 仍然看到 useState(0),但因为 React 记住你将 index 设置为 1,所以它返回 [1, setIndex]

    ¥Your component’s second render. React still sees useState(0), but because React remembers that you set index to 1, it returns [1, setIndex] instead.

  4. 以此类推!

    ¥And so on!

为组件提供多个状态变量

¥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}
      />
    </>
  );
}

如果状态变量不相关,那么拥有多个状态变量是个好主意,例如本例中的 indexshowMore。但是,如果你发现经常一起更改两个状态变量,则将它们合并为一个可能会更容易。例如,如果你有一个包含许多字段的表单,则使用一个包含对象的状态变量比每个字段的状态变量更方便。阅读 选择状态结构 了解更多提示。

¥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.

深入研究

React 如何知道要返回哪个状态?

¥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.

相反,为了实现简洁的语法,Hook 依赖于同一组件的每次渲染的稳定调用顺序。这在实践中效果很好,因为如果你遵循上面的规则 (“只在顶层调用钩子”),Hooks 将始终以相同的顺序被调用。此外,代码检查插件 可以捕捉到大多数错误。

¥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 钩子:不是魔法,只是数组。 中阅读有关此机制的更多信息。

¥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 状态的任何信息,甚至它是否有任何信息。与属性不同,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.

回顾

  • 当组件需要在渲染之间 “记住” 某些信息时使用状态变量。

    ¥Use a state variable when a component needs to “remember” some information between renders.

  • 状态变量是通过调用 useState 钩子来声明的。

    ¥State variables are declared by calling the useState Hook.

  • 钩子是以 use 开头的特殊函数。它们让你 “接入” React 特性,比如状态。

    ¥Hooks are special functions that start with use. They let you “hook into” React features like state.

  • 钩子可能会让你想起导入:它们需要无条件地被调用。调用钩子,包括 useState,只在一个组件的顶层或另一个钩子有效。

    ¥Hooks might remind you of imports: they need to be called unconditionally. Calling Hooks, including useState, is only valid at the top level of a component or another Hook.

  • useState 钩子返回一对值:当前状态和更新它的函数。

    ¥The useState Hook returns a pair of values: the current state and the function to update it.

  • 你可以拥有多个状态变量。在内部,React 按顺序匹配它们。

    ¥You can have more than one state variable. Internally, React matches them up by their order.

  • 状态对组件是私有的。如果你在两个地方渲染它,每个副本都有自己的状态。

    ¥State is private to the component. If you render it in two places, each copy gets its own state.

¥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}
      />
    </>
  );
}


React 中文网 - 粤ICP备13048890号