此文章是翻译Lists and Keys这篇React(版本v16.2.0)官方文档。
Lists and Keys
首先,让我们复习一下在JavaScript 中如何改变list。
给定代码如下,我们使用map()函数来接受一个numbers
数组并对其值进行加倍。我们将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 中,改变元素 数组的值几乎是一样的。
Rendering Multiple Components
你可以构建元素集合,在JSX 中(include them in JSX)使用大括号{}
来包含它们。
下面,我们通过JavaScript map()方法来循环遍历一个numbers
数组。为数组每一项返回一个<li>
元素。最后,我们将元素数组结果赋值给listItem
:
const numbers = [1, 2, 3, 4, 5]
const listItem = numbers.map((number)=>
<li>{number}</li>
);
我们将其全部listItems
数组包裹在<ul>
元素中,然后将其渲染到DOM 上):
ReactDOM.render(
<ul>{listItem}</ul>,
document.getElementById('root')
);
这段代码展示从1 到5 的带着重号的数字列表
Basic List Component
通常你会在一个组件)中渲染一个列表。
我们重构之前的例子为组件,此组件接受一个numbers
数组,输出也一个元素的无序列表。
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')
);
当你运行这段代码,会有一个警告提示,其需要一个key。一个"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')
);
Keys
Keys 帮助React 确认列表中哪一项发生改变,被添加或者是被移除。在数组中需要给元素一个稳定的身份(identify):
const numbers = [1, 2, 3, 4, 5];
const listItem = numbers.map((number)=>
<li key={number.toString()}>
{number}
</li>
);
最好的提供一个唯一标志的字符串在其兄弟节点中。大多数情况下你使用数据中的IDs 作为key:
const todoItemss = todos.map((todo)=>
<li key={todo.id}>
{todo.text}
</li>
);
当你不需要为渲染列表提供IDs 时,你可以使用列表索引作为最后的手段(a last resort):
const todoItemss = todos.map((todo, index)=>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
如果数据能够被重新排列,我们不建议使用索引作为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 的例子:
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify hte key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number)=>
// Wrong! The key should have been specified here:
<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) {
const value = props.value
return (
// Correct! There is no need to specify the key here:
<li>
{value}
</li>
)
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number)=>
// Correct! Key should be specified inside the array
<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')
);
一个好方法是map()
调用中的元素需要使用keys。
Keys Must Only Be Unique Among Sibings
在数组中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')
);
Keys 只是作为React 的线索它们不能传递给你的组件。如果你必须在你的组件中使用同样的值,请明确将其传给一个不同名字的prop:
const content = posts.map((post)=>
<Post
key={post.id}
id={post.id}
title={post.title}
/>
);
在上述例子中,这个Post
组件只能读取props.id
而不能读取props.key
Embedding map() in JSX
在上述例子中我们声明了一个不同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 允许在大括号里嵌入任何表达式embedding any expression,所以我们可以行内map()
结果。
function NumberList(props) {
const numbers = props.numbers
return (
<ul>
{numbers.map((number)=>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
)
}
有时在这清晰代码中结果,但是这种样式可能被滥用。像在JavaScript 中,它只是由你来决定提取一个变量是否更可读。记住如果map()
体中太嵌套了,提取一个组件)是更好的方法。