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

你将学习

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

从数组渲染数据

假设你有一个内容列表。

<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() 之类的方法来从它们渲染组件列表。

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

  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. 从组件中返回包装在<ul> 中的listItems
return <ul>{listItems}</ul>;

结果如下

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

请注意,上面的沙盒显示了一个控制台错误

控制台
警告:列表中的每个子项都应该有一个唯一的“key”属性。

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

过滤项目数组

这些数据可以更有条理地组织。

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)的项目的数组。

你只想要profession'chemist' 的项目。 此“测试”函数如下所示:(person) => person.profession === 'chemist'。 以下是如何将它们组合在一起

  1. 通过在people 上调用filter() 并使用person.profession === 'chemist' 过滤,创建一个仅包含“chemist”人员的新数组chemists
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 语句

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

但是,如果您的 => 后面跟着一个 { 花括号,则必须显式地写 return

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

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

使用 key 保持列表项的顺序

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

控制台
警告:列表中的每个子项都应该有一个唯一的“key”属性。

您需要为每个数组项提供一个 key — 一个字符串或数字,用于在该数组的其他项中唯一标识它

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

注意

直接位于 map() 调用内的 JSX 元素始终需要键!

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

您应该将键包含在数据中,而不是动态生成它们

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 节点

当每个项需要渲染多个 DOM 节点而不是一个时,您该怎么办?

简短的 <>...</> 片段 语法不允许您传递键,因此您需要将它们分组到单个 <div> 中,或者使用稍微长一点且更明确的 <Fragment> 语法:

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> 等组成的扁平列表。

从哪里获取您的 key

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

  • 来自数据库的数据:如果您的数据来自数据库,则可以使用数据库键/ID,它们本质上是唯一的。
  • 本地生成的数据:如果您的数据是在本地生成和持久化的(例如,笔记应用程序中的笔记),请在创建项时使用递增计数器、crypto.randomUUID()uuid 等包。

键的规则

  • 键在兄弟节点之间必须是唯一的。 但是,在不同数组中为 JSX 节点使用相同的键是可以的。
  • 键不能更改,否则会破坏它们的目的!不要在渲染时生成它们。

为什么 React 需要键?

假设您桌面上的文件没有名称。相反,您将按照它们的顺序来引用它们 — 第一个文件、第二个文件,依此类推。您可能会习惯它,但是一旦您删除了一个文件,它就会变得混乱。第二个文件将成为第一个文件,第三个文件将成为第二个文件,依此类推。

文件夹中的文件名和数组中的 JSX 键的作用类似。它们允许我们在兄弟节点之间唯一地标识一个项。精心选择的键比数组中的位置提供更多信息。即使由于重新排序导致位置发生变化,key 也可以让 React 在其生命周期内识别该项。

陷阱

您可能会 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,例如使用 key={Math.random()}。这将导致 key 在每次渲染时都不匹配,从而导致所有组件和 DOM 每次都被重新创建。这不仅速度慢,而且还会丢失列表项内的任何用户输入。请改用基于数据的稳定 ID。

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

总结

在本页,您学习了

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

挑战 1 4:
将列表分成两部分

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

将其更改为显示两个单独的列表,一个接一个:化学家其他人。像以前一样,您可以通过检查 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>
  );
}