建议配合官网文档 Lists and Keys 进行阅读。

Lists and Keys

下面给出了一个代码,使用map()函数将一个数组的每个值扩大至两倍:

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number * 2);
console.log(doubled);

我们将一个数组的每一个值都变成了其值的两倍,同样,我们也可以将每一个值都变成一个其对应的 React 元素。

Rendering Multiple Components

下面,我们通过map()函数将一个数组中的每一项变成一个<li>元素,即从包含数字的数组变成包含 React 元素的数组:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(
  number => <li>{number}</li>
);

我们将<li>数组中的元素放入<ul>元素,并渲染到 DOM 中,React 会自动展开数组:

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
)

这个代码将展示了1-5的<li>标签。

Basic List Component

一般来说,我们会在一个组件里渲染数组,因此我们将上面的代码重构为一个组件,该组件接受一个数组作为参数,返回一个<ul>的元素。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(
    number => <li>{number}</li>
  )
  return (
    <ul>{listItems}</ul>
  )
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(<NumberList numbers={numbers}/>, document.getElementById('root'));

当你运行这行代码时,会出现一个 warning:Warning: Each child in an array or iterator should have a unique "key" prop.,警告表示,在数组中的每个孩子都需要一个key参数,key是数组中每个数组项的一个特殊的字符串属性,让我们详细了解一下key的重要性。

Keys

key可以帮助 React 识别哪一个数组项被添加、删除、或者修改了,也就是说,通过key,React 可以在数组中唯一确定一个数组项:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(
    number =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(<NumberList numbers={numbers}/>, document.getElementById('root'));

对于一个数组项来说,最好的key是使用数组项的id属性,在实际应用中,数据往往是存放在数据库中的,因此使用数据库中的id字段能够保证数组项可以被唯一标识。

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

如果你的数组项没有类似id的固有属性,则你可以把数组下标作为最后的手段:

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

注意,当数组中的数组项顺序可能发生改变时,我们强烈建议使用数组下标作为key,这有可能对 React 的性能造成影响,或者引起组件的状态问题,详细的情况可以参考in-depth explanation on the negative impacts of using an index as a key.如果你不指定key的值,则 React 会默认使用数组下标作为key,所以还是尽可能找到每个数组项的固有属性作为key的值。

如果你想深入了解为什么key是必要的,可以参考in-depth explanation about why keys are necessary

Extracting Components with Keys

key只作用在数组的上下文中,这句话可能比较难理解,我们不考虑如下实例:
如果你提取了一个ListItem组件,则你只需要保证数组项ListItem拥有自己的key,而不必考虑给ListItem内部的元素设置key

错误的例子:

function ListItem(props) {
  const value = props.value;
  return (
    // 这是错误的,因为这里没有数组的概念,li 不是数组项 不需要在这里指定 key
    <li key={value.toString()}>
      {value}
    </li>
  )
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(number =>
    // 这是错误的,这里 listItems 是数组,因此需要在遍历的同时给数组项设置`key`
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  )
}

正确的写法应该如下所示:

function ListItem(props) {
  const value = props.value;
  // 这里不是数组项,因此不需要设置 key
  return (
    <li>{value}</li>
  )
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(
    // 这里是数组项,因此需要设置 Key
    number => <ListItems value={number} key={number.toString()}/>
  );
  return (
    <ul>
      {ListItems}
    </ul>
  )
}

一个很好的经验法则是:map()函数中生成的元素往往需要设定key

Keys Must Only Be Unique Among Siblings

key是数组项在数组中的唯一标识,他需要与它所在数组中是唯一的,但它并不需要全局唯一,也就是说,属于不同数组的数组项,其key允许是相同的。

function Blog(props) {
	const sidebar = (
		<ul>
			{props.posts.map(post =>
				<li key={post.id} >
					{post.title}
				</li>
			)}
		</ul>
	);

	const content = props.posts.map(post =>
		<div key={post.id}>
			<h3>{post.content}</h3>
		</div>
	);

	return (
		<div>
			{sidebar}
			<hr />
			{content}
		</div>
	)
}

const posts = [
	{id: 1, title: "title1", content: "content1"},
	{id: 2, title: "title2", content: "content2"},
];

ReactDOM.render(<Blog posts={posts}/>, document.getElementById('root'));

key虽然作为参数传递给了组件,但是由于key是专门用与 React 进行数组项识别的,因此在 props 里无法访问key,如果你想获取key的值,则需要明确的使用一个新参数进行传递。