🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 列表和 keys 首先,我们重温一下如何在 JavaScript 中转换列表。 对于下面代码,我们使用 [map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) 函数来操作下面数组的数值加倍。我们赋值通过 map() 返回的新数组到变量 doubled 并打印它: ~~~ const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map((number) => number * 2); console.log(doubled); ~~~ 这段代码打印 [2, 4, 6, 8, 10] 到控制台。 在 React 中,转换数组到[元素](https://facebook.github.io/react/docs/rendering-elements.html)列表几乎是相同的。 ## 渲染多个组件 使用花括号,可以以 JSX 的方式构建元素的集合并[包括](https://facebook.github.io/react/docs/introducing-jsx.html#embedding-expressions-in-jsx)它们。 下面,我们使用 JavaScript 的 map() 函数遍历数值数组。对于每个项返回一个 `<li>` 元素。最终,赋值结果数组到 listItems: ~~~ const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li>{number}</li> ); ~~~ 把整个 listItems 数组包含到一个 `<ul>` 元素,并[渲染到 DOM](https://facebook.github.io/react/docs/rendering-elements.html#rendering-an-element-into-the-dom): ~~~ ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') ); ~~~ 在 CodePen 中[打开查看](https://codepen.io/gaearon/pen/GjPyQr?editors=0011)。 这段代码显示一个从 1 到 5 的数字列表。 ## 基本列表组件 一般我们会在一个组件中渲染列表。 我们可以重构前面的例子到一个组件,它接受一个数值数组,并输出一个未排序的元素列表。 ~~~ 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') ); ~~~ 当你运行这段代码,将会给出一个警告:a key should be provided for list items 。一个 "key" 是一个特殊的字符串属性,当创建列表元素的时候需要包含的。我们将在下部分讨论为什么这是重要的。 我们在 numbers.map() 中赋值一个 key 给我们的列表元素,兵符这个丢失 key 的问题。 ~~~ 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') ); ~~~ 在 CodePen 中[打开查看](https://codepen.io/gaearon/pen/jrXYRR?editors=0011)。 ## keys keys 帮助 React 标识哪个项被修改、添加或者移除了。keys 应该在数组中被分配给元素来给定元素一个稳定的表示: ~~~ const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ); ~~~ 拾取 key 最好的方式是使用一个在它的同辈元素中不重复的标识字符串。多数情况你可以使用数据中的 IDs 作为 keys: ~~~ const todoItems = todos.map((todo) => <li key={todo.id}> {todo.text} </li> ); ~~~ 当你没有稳定的 IDs 来渲染元素,你可以使用项的索引作为 key 作为最后的选择: ~~~ const todoItems = todos.map((todo, index) => // 只有项没有稳定的 IDs 时候这样做 <li key={index}> {todo.text} </li> ); ~~~ 如果项可能被重新排序,我们不建议使用索引作为 keys,因为这是很慢的。如果感兴趣,你可以阅读[一个深入的介绍关于为什么 keys 是必须的](https://facebook.github.io/react/docs/reconciliation.html#recursing-on-children)。 ## 使用 keys 提取组件 keys 只使用在数组相关的上下文环境。 例如,如果你[提取](https://facebook.github.io/react/docs/components-and-props.html#extracting-components)一个 ListItem 组件,应该在 `<ListItem />` 元素中保存 key,而不是在 ListItem 中的根的 `<li>` 元素。 **例子:错误的 key 用法** ~~~ function ListItem(props) { const value = props.value; return ( // 错误!不需要在这里指定 key: <li key={value.toString()}> {value} </li> ); } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // 错误!key 应该在这里指定: <ListItem value={number} /> ); return ( <ul> {listItems} </ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') ); ~~~ **例子:正确的 key 用法** ~~~ function ListItem(props) { // 正确!不需要从这里指定 keyi: return <li>{props.value}</li>; } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // 正确!key 应该被从这里指定 <ListItem key={number.toString()} value={number} /> ); return ( <ul> {listItems} </ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') ); ~~~ 在 CodePen 中[打开查看](https://codepen.io/rthor/pen/QKzJKG?editors=0010)。 在 map() 调用中的元素需要 keys,这是一条不错的经验准则。 ## keys 在同辈元素之间必须唯一 在数组中使用的 keys 必须在它们的同辈之间唯一。然而它们并不需要全局唯一。我们可以在操作两个不同数组的时候使用相同的 keys : ~~~ 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.title}</h3> <p>{post.content}</p> </div> ); return ( <div> {sidebar} <hr /> {content} </div> ); } const posts = [ {id: 1, title: 'Hello World', content: 'Welcome to learning React!'}, {id: 2, title: 'Installation', content: 'You can install React from npm.'} ]; ReactDOM.render( <Blog posts={posts} />, document.getElementById('root') ); ~~~ 在 CodePen 中[打开查看](https://codepen.io/gaearon/pen/NRZYGN?editors=0010)。 keys 作为 React 的一个提示 ,但是它们并不会传递到你的组件中。如果你在组件中需要相同的值,使用一个不同的名称明确的传递为一个 prop: ~~~ const content = posts.map((post) => <Post key={post.id} id={post.id} title={post.title} /> ); ~~~ 上面的例子中, Post 组件可以读取 props.id,但是不能读取 props.key 。 ## 在 JSX 中嵌入 map() 在上面的例子中,我们声明了一个单独的 listItems 变量,并在 JSX 中包含了它: ~~~ function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <ListItem key={number.toString()} value={number} /> ); return ( <ul> {listItems} </ul> ); } ~~~ JSX 可以[嵌入任何表达式到花括号中](https://facebook.github.io/react/docs/introducing-jsx.html#embedding-expressions-in-jsx),所以我们可以内联 map() 的结果: ~~~ function NumberList(props) { const numbers = props.numbers; return ( <ul> {numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul> ); } ~~~ 在 CodePen 中[打开查看](https://codepen.io/gaearon/pen/BLvYrB?editors=0010)。 有时这可以产生清晰的代码,但是这个风格也可能被滥用。就在在 JavaScript 中,是否值得为了可读性提取一个变量这取决于你。但是记住,如果 map() 体有太多嵌套,可能是[提取组件](https://facebook.github.io/react/docs/components-and-props.html#extracting-components)的良好时机。