此文章是翻译Lists and Keys这篇React(版本v16.2.0)官方文档。

Lists and Keys

首先,让我们复习一下在JavaScript 中如何改变list。

给定代码如下,我们使用map()函数来接受一个numbers 数组并对其值进行加倍。我们将map()函数的返回值赋给doubled 并打印这个值:

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

代码输出值为[2, 4, 6, 8, 10]

在React 中,改变元素 数组的值几乎是一样的。

Rendering Multiple Components

你可以构建元素集合,在JSX 中(include them in JSX)使用大括号{} 来包含它们。

下面,我们通过JavaScript map()方法来循环遍历一个numbers 数组。为数组每一项返回一个<li> 元素。最后,我们将元素数组结果赋值给listItem

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

我们将其全部listItems 数组包裹在<ul> 元素中,然后将其渲染到DOM 上):

  1. ReactDOM.render(
  2. <ul>{listItem}</ul>,
  3. document.getElementById('root')
  4. );

在CodePen 上尝试

这段代码展示从1 到5 的带着重号的数字列表

Basic List Component

通常你会在一个组件)中渲染一个列表。

我们重构之前的例子为组件,此组件接受一个numbers 数组,输出也一个元素的无序列表。

  1. function NumberList(props) {
  2. const numbers = props.numbers;
  3. const listItems = numbers.map((number)=>
  4. <li>{number}</li>
  5. );
  6. return (
  7. <ul>{listItems}</ul>
  8. );
  9. }
  10. const numbers = [1, 2, 3, 4, 5];
  11. ReactDOM.render(
  12. <NumberList numbers={numbers} />,
  13. document.getElementById('root')
  14. );

当你运行这段代码,会有一个警告提示,其需要一个key。一个”key” 作为一个重要的特性在创建的元素。在下一节,我们来讨论为什么它非常重要。

让我们在numbers.map() 中赋予一个key 来解决缺失key 问题。

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

在CodePen 上尝试

Keys

Keys 帮助React 确认列表中哪一项发生改变,被添加或者是被移除。在数组中需要给元素一个稳定的身份(identify):

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

最好的提供一个唯一标志的字符串在其兄弟节点中。大多数情况下你使用数据中的IDs 作为key:

  1. const todoItemss = todos.map((todo)=>
  2. <li key={todo.id}>
  3. {todo.text}
  4. </li>
  5. );

当你不需要为渲染列表提供IDs 时,你可以使用列表索引作为最后的手段(a last resort):

  1. const todoItemss = todos.map((todo, index)=>
  2. // Only do this if items have no stable IDs
  3. <li key={index}>
  4. {todo.text}
  5. </li>
  6. );

如果数据能够被重新排列,我们不建议使用索引作为keys。这会对性能产生负面影响,并可能导致组件状态问题。查看Robin Pokorny 的文章in-depth explanation on the negative impacts of using an index as a key。如果你选择不给列表条目赋值一个名曲的key,那么React 将默认使用索引作为key。

这有一篇文章in-depth explanition about why keys are necessary,如果你有兴趣了解更多。

Extracting Components with Keys

Keys 只有在数组的上下文中才有意义。

例如,如果你提取了一个ListItem 组件,你应该在数组的<ListItem /> 元素上使用这个key,而不是在ListItem 自身根的<li> 元素上。

错误的使用Key 的例子:

  1. function ListItem(props) {
  2. const value = props.value;
  3. return (
  4. // Wrong! There is no need to specify hte key here:
  5. <li key={value.toString()}>
  6. {value}
  7. </li>
  8. );
  9. }
  10. function NumberList(props) {
  11. const numbers = props.numbers;
  12. const listItems = numbers.map((number)=>
  13. // Wrong! The key should have been specified here:
  14. <ListItem value={number}/>
  15. );
  16. return(
  17. <ul>
  18. {listItems}
  19. </ul>
  20. );
  21. }
  22. const numbers = [1, 2, 3, 4, 5];
  23. ReactDOM.render(
  24. <NumberList numbers={numbers} />,
  25. document.getElementById('root')
  26. );

正确使用Key 的例子:

  1. function ListItem(props) {
  2. const value = props.value
  3. return (
  4. // Correct! There is no need to specify the key here:
  5. <li>
  6. {value}
  7. </li>
  8. )
  9. }
  10. function NumberList(props) {
  11. const numbers = props.numbers;
  12. const listItems = numbers.map((number)=>
  13. // Correct! Key should be specified inside the array
  14. <ListItem key={number.toString()}
  15. value={number}/>
  16. );
  17. return(
  18. <ul>
  19. {listItems}
  20. </ul>
  21. );
  22. }
  23. const numbers = [1, 2, 3, 4, 5];
  24. ReactDOM.render(
  25. <NumberList numbers={numbers} />,
  26. document.getElementById('root')
  27. );

在CodePen 上尝试

一个好方法是map()调用中的元素需要使用keys。

Keys Must Only Be Unique Among Sibings

在数组中Keys 的使用必须是在它们兄弟中唯一的。即时它们不是全局唯一。我们可以使用相同的keys 在不同的数组中:

  1. function Blog(props) {
  2. const sidebar = (
  3. <ul>
  4. {props.posts.map((post)=>
  5. <li key={post.id}>
  6. {post.title}
  7. </li>
  8. )}
  9. </ul>
  10. );
  11. const content = props.posts.map((post)=>
  12. <div key={post.id}>
  13. <h3>{post.title}</h3>
  14. <p>{post.content}</p>
  15. </div>
  16. );
  17. return (
  18. <div>
  19. {sidebar}
  20. <hr />
  21. {content}
  22. </div>
  23. );
  24. }
  25. const posts = [
  26. {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  27. {id: 2, title: 'Installation', content: 'You can install React from npm.'}
  28. ];
  29. ReactDOM.render(
  30. <Blog posts={posts} />,
  31. document.getElementById('root')
  32. );

在CodePen 上尝试

Keys 只是作为React 的线索它们不能传递给你的组件。如果你必须在你的组件中使用同样的值,请明确将其传给一个不同名字的prop:

  1. const content = posts.map((post)=>
  2. <Post
  3. key={post.id}
  4. id={post.id}
  5. title={post.title}
  6. />
  7. );

在上述例子中,这个Post 组件只能读取props.id 而不能读取props.key

Embedding map() in JSX

在上述例子中我们声明了一个不同listItems 变量在JSX 中。

  1. function NumberList(props) {
  2. const numbers = props.numbers;
  3. const listItems = numbers.map((number)=>
  4. <ListItem key={number.toString()}
  5. value={number}/>
  6. );
  7. return(
  8. <ul>
  9. {listItems}
  10. </ul>
  11. );
  12. }

JSX 允许在大括号里嵌入任何表达式embedding any expression,所以我们可以行内map() 结果。

  1. function NumberList(props) {
  2. const numbers = props.numbers
  3. return (
  4. <ul>
  5. {numbers.map((number)=>
  6. <ListItem key={number.toString()}
  7. value={number} />
  8. )}
  9. </ul>
  10. )
  11. }

在CodePen 上尝试

有时在这清晰代码中结果,但是这种样式可能被滥用。像在JavaScript 中,它只是由你来决定提取一个变量是否更可读。记住如果map() 体中太嵌套了,提取一个组件)是更好的方法。