你经常会想要从一组数据中显示多个相似的组件。你可以使用 JavaScript 数组方法 来操作数据数组。在本页中,你将使用 filter()map() 结合 React 来过滤和转换你的数据数组为组件数组。

🌐 You will often want to display multiple similar components from a collection of data. You can use the JavaScript array methods to manipulate an array of data. On this page, you’ll use filter() and map() with React to filter and transform your array of data into an array of components.

你将学习到

  • 如何使用 JavaScript 的 map() 从数组中渲染组件
  • 如何使用 JavaScript 的 filter() 仅渲染特定组件
  • 何时以及为何使用 React 键

从数组中渲染数据

🌐 Rendering data from arrays

假设你有一个内容列表。

🌐 Say that you have a list of content.

<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>

这些列表项之间唯一的区别是它们的内容,它们的数据。在构建界面时,你通常需要使用不同的数据展示相同组件的多个实例:从评论列表到个人资料图片图库。在这些情况下,你可以将这些数据存储在 JavaScript 对象和数组中,并使用像 map()filter() 这样的方法从中渲染组件列表。

🌐 The only difference among those list items is their contents, their data. You will often need to show several instances of the same component using different data when building interfaces: from lists of comments to galleries of profile images. In these situations, you can store that data in JavaScript objects and arrays and use methods like map() and filter() to render lists of components from them.

以下是如何从数组生成条目列表的简短示例:

🌐 Here’s a short example of how to generate a list of items from an array:

  1. 数据移动到数组中:
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
  1. people 成员映射到一个新的 JSX 节点数组中,listItems
const listItems = people.map(person => <li>{person}</li>);
  1. 从你的组件中 listItems 返回,并用 <ul> 封装:
return <ul>{listItems}</ul>;

这是结果:

🌐 Here is the result:

const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

请注意上面的沙箱显示控制台错误:

🌐 Notice the sandbox above displays a console error:

Console

你将会在本页稍后学习如何修复这个错误。在此之前,让我们给你的数据添加一些结构。

🌐 You’ll learn how to fix this error later on this page. Before we get to that, let’s add some structure to your data.

过滤条目数组

🌐 Filtering arrays of items

这些数据甚至可以更加结构化。

🌐 This data can be structured even more.

const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];

假设你想要一种方法,只显示职业是 'chemist' 的人。你可以使用 JavaScript 的 filter() 方法来返回仅包含这些人的数组。这个方法接受一个项目数组,通过“测试”(一个返回 truefalse 的函数)来处理它们,并返回一个只包含通过测试的项目(返回 true)的新数组。

🌐 Let’s say you want a way to only show people whose profession is 'chemist'. You can use JavaScript’s filter() method to return just those people. This method takes an array of items, passes them through a “test” (a function that returns true or false), and returns a new array of only those items that passed the test (returned true).

你只想要那些 profession'chemist' 的项目。用于此的“测试”函数看起来像 (person) => person.profession === 'chemist'。以下是如何组合它的方法:

🌐 You only want the items where profession is 'chemist'. The “test” function for this looks like (person) => person.profession === 'chemist'. Here’s how to put it together:

  1. 创建 一个只包含“化学家”人员的新数组 chemists,方法是对 people 调用 filter(),按 person.profession === 'chemist' 进行过滤:
const chemists = people.filter(person =>
person.profession === 'chemist'
);
  1. 现在在 chemists映射:
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
  1. 最后,从你的组件中返回 listItems
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemist'
  );
  const listItems = chemists.map(person =>
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return <ul>{listItems}</ul>;
}

易犯错误

箭头函数会隐式返回 => 之后的表达式,因此你不需要 return 语句:

🌐 Arrow functions implicitly return the expression right after =>, so you didn’t need a return statement:

const listItems = chemists.map(person =>
<li>...</li> // Implicit return!
);

但是,如果你的 => 后面跟着一个 { 大括号,你必须显式写出 return

🌐 However, you must write return explicitly if your => is followed by a { curly brace!

const listItems = chemists.map(person => { // Curly brace
return <li>...</li>;
});

包含 => { 的箭头函数被称为具有 “块级主体”。 它们允许你编写多于一行的代码,但你必须自己编写一个 return 语句。如果你忘记了,则不会返回任何东西!

🌐 Arrow functions containing => { are said to have a “block body”. They let you write more than a single line of code, but you have to write a return statement yourself. If you forget it, nothing gets returned!

使用 key 保持列表项的顺序

🌐 Keeping list items in order with key

请注意,上面的所有沙箱都在控制台中显示错误:

🌐 Notice that all the sandboxes above show an error in the console:

Console

你需要给每个数组项一个 key —— 一个在该数组中唯一标识它的字符串或数字:

🌐 You need to give each array item a key — a string or a number that uniquely identifies it among other items in that array:

<li key={person.id}>...</li>

注意

直接在 map() 调用中的 JSX 元素总是需要 key!

🌐 JSX elements directly inside a map() call always need keys!

键告诉 React 每个组件对应数组中的哪一项,以便它之后能够将它们匹配起来。如果你的数组项可能会移动(例如由于排序)、被插入或被删除,这一点就显得很重要。一个选择得当的 key 可以帮助 React 推断究竟发生了什么,并对 DOM 树进行正确的更新。

🌐 Keys tell React which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps React infer what exactly has happened, and make the correct updates to the DOM tree.

与其即时生成键,不如将它们包含在数据中:

🌐 Rather than generating keys on the fly, you should include them in your data:

export const people = [{
  id: 0, // Used in JSX as a key
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
  accomplishment: 'spaceflight calculations',
  imageId: 'MK3eW3A'
}, {
  id: 1, // Used in JSX as a key
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
  accomplishment: 'discovery of Arctic ozone hole',
  imageId: 'mynHUSa'
}, {
  id: 2, // Used in JSX as a key
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
  accomplishment: 'electromagnetism theory',
  imageId: 'bE7W1ji'
}, {
  id: 3, // Used in JSX as a key
  name: 'Percy Lavon Julian',
  profession: 'chemist',
  accomplishment: 'pioneering cortisone drugs, steroids and birth control pills',
  imageId: 'IOjWm71'
}, {
  id: 4, // Used in JSX as a key
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
  accomplishment: 'white dwarf star mass calculations',
  imageId: 'lrWQx8l'
}];

深入研究

为每个列表项显示多个 DOM 节点

🌐 Displaying several DOM nodes for each list item

当每个条目需要渲染的不是一个而是多个 DOM 节点时,你会怎么做?

🌐 What do you do when each item needs to render not one, but several DOM nodes?

<>...</> Fragment 语法不允许你传递 key,因此你需要要么将它们分组到单个 <div> 中,要么使用稍长一些的 更明确的 <Fragment> 语法:

🌐 The short <>...</> Fragment syntax won’t let you pass a key, so you need to either group them into a single <div>, or use the slightly longer and more explicit <Fragment> syntax:

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);

片段从 DOM 中消失,所以这将生成一个扁平的列表,包括 <h1><p><h1><p>,以此类推。

🌐 Fragments disappear from the DOM, so this will produce a flat list of <h1>, <p>, <h1>, <p>, and so on.

在哪里获取你的 key

🌐 Where to get your key

不同的数据来源提供不同的键来源:

🌐 Different sources of data provide different sources of keys:

  • 来自数据库的数据: 如果你的数据来自数据库,你可以使用数据库的键/ID,这些本质上是唯一的。
  • 本地生成的数据: 如果你的数据是在本地生成和保存的(例如记事应用中的注意),在创建项目时使用递增计数器、crypto.randomUUID() 或类似 uuid 的软件包。

键的规则

🌐 Rules of keys

  • 键在同级中必须唯一。 但是,在_不同_数组中的 JSX 节点使用相同的键是可以的。
  • 键不能更改,否则就失去了它们的意义!渲染时不要生成它们。

为什么 React 需要键?

🌐 Why does React need keys?

想象一下,你桌面上的文件没有名字。相反,你会按顺序称呼它们——第一个文件、第二个文件,以此类推。你可能会习惯这种方式,但一旦你删除一个文件,就会变得混乱。第二个文件会变成第一个文件,第三个文件会变成第二个文件,以此类推。

🌐 Imagine that files on your desktop didn’t have names. Instead, you’d refer to them by their order — the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on.

文件夹中的文件名和数组中的 JSX 键具有相似的作用。它们让我们能够在同级元素中唯一标识一个项目。一个精心选择的键比数组中的位置提供更多信息。即使由于重新排序而更改了_位置_,key 仍然让 React 在整个生命周期中识别该项目。

🌐 File names in a folder and JSX keys in an array serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets React identify the item throughout its lifetime.

易犯错误

你可能会想使用数组中某个项的索引作为它的键。事实上,如果你根本没有指定 key,React 就会使用索引。但是,如果插入、删除项或数组被重新排序,渲染项的顺序会随时间改变。将索引作为键常常会导致微妙且令人困惑的错误。

🌐 You might be tempted to use an item’s index in the array as its key. In fact, that’s what React will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs.

同样,不要动态生成键,例如使用 key={Math.random()}。这会导致每次渲染时键都不匹配,从而每次都会重新创建所有组件和 DOM。这不仅会很慢,还会丢失列表项中的任何用户输入。相反,应使用基于数据的稳定 ID。

🌐 Similarly, do not generate keys on the fly, e.g. with key={Math.random()}. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data.

请注意,你的组件不会将 key 作为 prop 接收。它仅由 React 本身用作提示。如果你的组件需要 ID,你必须将其作为单独的 prop 传递:<Profile key={id} userId={id} />

🌐 Note that your components won’t receive key as a prop. It’s only used as a hint by React itself. If your component needs an ID, you have to pass it as a separate prop: <Profile key={id} userId={id} />.

回顾

在此页面上,你了解到:

🌐 On this page you learned:

  • 如何将数据从组件中移出并移入数组和对象等数据结构中。
  • 如何使用 JavaScript 的 map() 生成一组相似的组件。
  • 如何使用 JavaScript 的 filter() 创建过滤项的数组。
  • 为什么以及如何在集合中的每个组件上设置 key,以便即使它们的位置或数据发生变化,React 也能跟踪每个组件。

挑战 1 of 4:
将列表一分为二

🌐 Splitting a list in two

此示例显示所有人的列表。

🌐 This example shows a list of all people.

将其更改为显示两个连续的独立列表:化学家其他人。 像以前一样,你可以通过检查 person.profession === 'chemist' 来判断某人是否是化学家。

🌐 Change it to show two separate lists one after another: Chemists and Everyone Else. Like previously, you can determine whether a person is a chemist by checking if person.profession === 'chemist'.

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientists</h1>
      <ul>{listItems}</ul>
    </article>
  );
}