添加交互性

屏幕上的某些内容会响应用户输入而更新。例如,单击图片库会切换活动图片。在 React 中,随时间变化的数据称为状态。你可以向任何组件添加状态,并根据需要更新它。在本章中,你将学习如何编写组件来处理交互、更新它们的状态以及随时间显示不同的输出。

¥Some things on the screen update in response to user input. For example, clicking an image gallery switches the active image. In React, data that changes over time is called state. You can add state to any component, and update it as needed. In this chapter, you’ll learn how to write components that handle interactions, update their state, and display different output over time.

响应事件

¥Responding to events

React 允许你向 JSX 添加事件处理程序。事件处理程序是你自己的函数,将被触发以响应用户交互,例如单击、悬停、关注表单输入等。

¥React lets you add event handlers to your JSX. Event handlers are your own functions that will be triggered in response to user interactions like clicking, hovering, focusing on form inputs, and so on.

<button> 这样的内置组件只支持像 onClick 这样的浏览器内置事件。但是,你也可以创建自己的组件,并为它们的事件处理程序提供你喜欢的任何特定于应用的名称。

¥Built-in components like <button> only support built-in browser events like onClick. However, you can also create your own components, and give their event handler props any application-specific names that you like.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

准备好学习这个主题了吗?

阅读 响应事件 以了解如何添加事件处理程序。

¥Read Responding to Events to learn how to add event handlers.

阅读更多

状态:组件的内存

¥State: a component’s memory

作为交互的结果,组件通常需要更改屏幕上的内容。在表单中输入应该更新输入字段,单击图片轮播上的 “下一个” 应该更改显示的图片,单击 “购买” 将产品放入购物车。组件需要 “记住” 的东西:当前输入值,当前图片,购物车。在 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” puts 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 钩子将状态添加到组件。钩子是让你的组件使用 React 功能的特殊函数(状态是这些功能之一)。useState 钩子允许你声明一个状态变量。它采用初始状态并返回一对值:当前状态,以及一个让你更新它的状态设置函数。

¥You can add state to a component with a useState Hook. Hooks are special functions that let your components use React features (state is one of those features). The useState Hook lets you declare a state variable. It takes the initial state and returns a pair of values: the current state, and a state setter function that lets you update it.

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);

以下是图片库如何在点击时使用和更新状态:

¥Here is how an image gallery uses and updates state on click:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

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

准备好学习这个主题了吗?

阅读 状态:组件的内存 以了解如何记住一个值并在交互时更新它。

¥Read State: A Component’s Memory to learn how to remember a value and update it on interaction.

阅读更多

渲染和提交

¥Render and commit

在你的组件显示在屏幕上之前,它们必须被 React 渲染。了解此过程中的步骤将帮助你思考代码的执行方式并解释其行为。

¥Before your components are displayed on the screen, they must be rendered by React. Understanding the steps in this process will help you think about how your code executes and explain its behavior.

想象一下,你的组件是厨房里的厨师,用食材烹制美味佳肴。在这个场景中,React 是一个服务员,负责接收客户的请求并为他们带来订单。这个请求和提供 UI 的过程分为三个步骤:

¥Imagine that your components are cooks in the kitchen, assembling tasty dishes from ingredients. In this scenario, React is the waiter who puts in requests from customers and brings them their orders. This process of requesting and serving UI has three steps:

  1. 触发渲染(将用餐者的订单传送到厨房)

    ¥Triggering a render (delivering the diner’s order to the kitchen)

  2. 渲染组件(在厨房准备订单)

    ¥Rendering the component (preparing the order in the kitchen)

  3. 提交给 DOM(将订单放在表格上)

    ¥Committing to the DOM (placing the order on the table)

  1. React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen.
    Trigger
  2. The Card Chef gives React a fresh Card component.
    Render
  3. React delivers the Card to the user at their table.
    Commit

Illustrated by Rachel Lee Nabors

准备好学习这个主题了吗?

阅读 渲染和提交 以了解 UI 更新的生命周期。

¥Read Render and Commit to learn the lifecycle of a UI update.

阅读更多

状态快照

¥State as a snapshot

与常规 JavaScript 变量不同,React 状态的行为更像是快照。设置它不会更改你已有的状态变量,而是会触发重新渲染。起初这可能令人惊讶!

¥Unlike regular JavaScript variables, React state behaves more like a snapshot. Setting it does not change the state variable you already have, but instead triggers a re-render. This can be surprising at first!

console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!

此行为可帮助你避免细微的错误。这是一个小聊天应用。尝试猜测如果你先按 “发送” 然后将收件人更改为 Bob 会发生什么。五秒后 alert 中会出现谁的名字?

¥This behavior helps you avoid subtle bugs. Here is a little chat app. Try to guess what happens if you press “Send” first and then change the recipient to Bob. Whose name will appear in the alert five seconds later?

import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`You said ${message} to ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        To:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

准备好学习这个主题了吗?

阅读 状态快照 以了解为什么状态在事件处理程序中显示为 “固定” 且不变。

¥Read State as a Snapshot to learn why state appears “fixed” and unchanging inside the event handlers.

阅读更多

队列一系列状态更新

¥Queueing a series of state updates

这个组件有问题:单击 “+3” 只会增加一次分数。

¥This component is buggy: clicking “+3” increments the score only once.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

状态快照 解释了为什么会这样。设置状态请求新的重新渲染,但不会在已经运行的代码中更改它。因此,在你调用 setScore(score + 1) 之后,score 仍然是 0

¥State as a Snapshot explains why this is happening. Setting state requests a new re-render, but does not change it in the already running code. So score continues to be 0 right after you call setScore(score + 1).

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0

你可以通过在设置状态时传递更新程序函数来解决此问题。请注意如何用 setScore(s => s + 1) 替换 setScore(score + 1) 来修复 “+3” 按钮。这使你可以对多个状态更新进行排队。

¥You can fix this by passing an updater function when setting state. Notice how replacing setScore(score + 1) with setScore(s => s + 1) fixes the “+3” button. This lets you queue multiple state updates.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}

准备好学习这个主题了吗?

阅读 队列一系列状态更新 以了解如何对一系列状态更新进行排队。

¥Read Queueing a Series of State Updates to learn how to queue a sequence of state updates.

阅读更多

更新状态中的对象

¥Updating objects in state

状态可以保存任何类型的 JavaScript 值,包括对象。但是你不应该直接改变你在 React 状态下持有的对象和数组。而是,当你想更新一个对象和数组时,你需要创建一个新的(或复制一个现有的),然后更新状态以使用该副本。

¥State can hold any kind of JavaScript value, including objects. But you shouldn’t change objects and arrays that you hold in the React state directly. Instead, when you want to update an object and array, you need to create a new one (or make a copy of an existing one), and then update the state to use that copy.

通常,你将使用 ... 扩展语法来复制要更改的对象和数组。例如,更新嵌套对象可能如下所示:

¥Usually, you will use the ... spread syntax to copy objects and arrays that you want to change. For example, updating a nested object could look like this:

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

如果在代码中复制对象变得乏味,你可以使用像 Immer 这样的库来减少重复代码:

¥If copying objects in code gets tedious, you can use a library like Immer to reduce repetitive code:

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}

准备好学习这个主题了吗?

阅读 更新状态中的对象 以了解如何正确更新对象。

¥Read Updating Objects in State to learn how to update objects correctly.

阅读更多

更新状态数组

¥Updating arrays in state

数组是另一种类型的可变 JavaScript 对象,你可以将其存储在状态中,并应将其视为只读。就像对象一样,当你想更新存储在状态中的数组时,你需要创建一个新数组(或复制现有数组),然后设置状态以使用新数组:

¥Arrays are another type of mutable JavaScript objects you can store in state and should treat as read-only. Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array:

import { useState } from 'react';

const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

如果在代码中复制数组变得乏味,你可以使用像 Immer 这样的库来减少重复代码:

¥If copying arrays in code gets tedious, you can use a library like Immer to reduce repetitive code:

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}

准备好学习这个主题了吗?

阅读 更新状态中的数组 以了解如何正确更新数组。

¥Read Updating Arrays in State to learn how to update arrays correctly.

阅读更多

下一步是什么?

¥What’s next?

前往 响应事件 开始逐页阅读本章!

¥Head over to Responding to Events to start reading this chapter page by page!

或者,如果你已经熟悉这些主题,为什么不阅读 管理状态

¥Or, if you’re already familiar with these topics, why not read about Managing State?


React 中文网 - 粤ICP备13048890号