01-React基础入门——07-列表渲染与 Key
·

列表渲染与 Key
一、什么是列表渲染?
在 React 中,列表渲染是指使用 JavaScript 的数组方法来生成一组元素。最常见的是使用 map() 方法遍历数组,返回 JSX 元素。
核心概念
- 使用
map()、filter()、reduce()等数组方法处理数据 - 每个列表项都需要一个唯一的
key属性 - React 使用 key 来识别哪些元素发生了变化
二、基础列表渲染
2.1 使用 map() 渲染列表
function NumberList({ numbers }) {
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return <ul>{listItems}</ul>;
}
// 使用
<NumberList numbers={[1, 2, 3, 4, 5]} />
2.2 直接在 JSX 中使用 map
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
);
}
2.3 使用 filter() 过滤数据
function CompletedTodos({ todos }) {
const completedTodos = todos.filter(todo => todo.completed);
return (
<ul>
{completedTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
三、Key 的深入理解
3.1 什么是 Key?
Key 是 React 用来识别列表项的唯一标识符。它帮助 React 确定哪些元素被添加、更改或删除。
3.2 Key 的作用
没有 key 时:
[1,2,3] → [1,2,3,4] React 会重新渲染整个列表
有 key 时:
[1,2,3] → [1,2,3,4] React 只添加新的第 4 项
3.3 选择合适的 Key
// ✅ 好的 Key:使用数据中的唯一 ID
<li key={todo.id}>{todo.text}</li>
// ✅ 可以:使用索引(仅在列表静态且不会重新排序时)
<li key={index}>{todo.text}</li>
// ❌ 坏的 Key:使用随机数或 Math.random()
<li key={Math.random()}>{todo.text}</li>
3.4 Key 的最佳实践
| 场景 | 推荐 Key | 说明 |
|---|---|---|
| 数据库数据 | 数据库 ID | 最稳定 |
| 本地生成数据 | uuid / nanoid | 保证唯一性 |
| 静态列表 | 索引(谨慎) | 列表不变时可接受 |
| 动态列表 | 唯一标识符 | 必须稳定且唯一 |
四、列表渲染示例
4.1 渲染对象数组
const users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
];
function UserList() {
return (
<table>
<thead>
<tr><th>姓名</th><th>年龄</th></tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.age}</td>
</tr>
))}
</tbody>
</table>
);
}
4.2 带删除功能的列表
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React' },
{ id: 2, text: '写代码' },
{ id: 3, text: '休息' }
]);
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
);
}
4.3 可编辑列表
function EditableList() {
const [items, setItems] = useState(['项目 1', '项目 2', '项目 3']);
const [newItem, setNewItem] = useState('');
const addItem = () => {
if (newItem.trim()) {
setItems([...items, newItem]);
setNewItem('');
}
};
return (
<div>
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<input
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
placeholder="添加新项目"
/>
<button onClick={addItem}>添加</button>
</div>
);
}
4.4 分组列表
const groupedData = {
'已完成': [
{ id: 1, text: '任务 1' },
{ id: 2, text: '任务 2' }
],
'未完成': [
{ id: 3, text: '任务 3' },
{ id: 4, text: '任务 4' }
]
};
function GroupedList() {
return (
<div>
{Object.entries(groupedData).map(([group, items]) => (
<div key={group}>
<h3>{group}</h3>
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</div>
))}
</div>
);
}
五、性能优化
5.1 使用 React.memo 优化列表项
const TodoItem = React.memo(({ todo, onToggle }) => {
console.log('渲染:', todo.id);
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{todo.text}
</li>
);
});
function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
/>
))}
</ul>
);
}
5.2 虚拟列表(大数据量)
对于包含大量数据的列表(>1000项),建议使用虚拟列表库:
npm install react-window
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style, data }) => (
<div style={style}>
{data[index]}
</div>
);
function VirtualList({ items }) {
return (
<List
height={400}
itemCount={items.length}
itemSize={35}
itemData={items}
width={300}
>
{Row}
</List>
);
}
六、常见问题与陷阱
6.1 使用索引作为 Key 的问题
// ❌ 当列表顺序变化时会有问题
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}
// 问题:
// 1. 删除第一项时,所有后续项的 key 都改变了
// 2. React 会重新渲染所有项,而不是只删除一项
// 3. 可能导致组件状态错乱
6.2 Key 不唯一的问题
// ❌ Key 必须唯一
{items.map(item => (
<div key={item.type}> {/* 如果有相同 type 会报错 */}
{item.value}
</div>
))}
// ✅ 使用组合 key
{items.map(item => (
<div key={`${item.type}-${item.id}`}>
{item.value}
</div>
))}
6.3 Key 不会传递给组件
// ❌ 不能通过 props 获取 key
function MyComponent(props) {
console.log(props.key); // undefined
return <div>{props.value}</div>;
}
// ✅ 显式传递
{items.map(item => (
<MyComponent key={item.id} id={item.id} value={item.value} />
))}
七、高级技巧
7.1 使用 key 重置组件状态
function Profile({ userId }) {
// 当 userId 改变时,key 改变,组件会重新创建
return (
<UserProfile
key={userId} // 强制重新挂载
userId={userId}
/>
);
}
7.2 列表动画
import { TransitionGroup, CSSTransition } from 'react-transition-group';
function AnimatedList({ items }) {
return (
<TransitionGroup component="ul">
{items.map(item => (
<CSSTransition
key={item.id}
timeout={500}
classNames="item"
>
<li>{item.text}</li>
</CSSTransition>
))}
</TransitionGroup>
);
}
八、练习题
基础题
- 渲染一个水果列表,每个水果旁边显示一个"购买"按钮
- 实现一个待办事项列表,可以添加新事项
进阶题
- 实现一个可拖拽排序的列表
- 实现一个带有搜索过滤功能的列表
参考答案
// 1. 水果列表
function FruitList() {
const fruits = ['苹果', '香蕉', '橙子', '葡萄'];
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>
{fruit} <button>购买</button>
</li>
))}
</ul>
);
}
// 2. 带搜索的列表
function SearchableList() {
const [searchTerm, setSearchTerm] = useState('');
const items = ['React', 'Vue', 'Angular', 'Svelte'];
const filteredItems = items.filter(item =>
item.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="搜索..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<ul>
{filteredItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
九、小结
| 要点 | 说明 |
|---|---|
| map() | 最常用的列表渲染方法 |
| key | 必须唯一且稳定,帮助 React 识别变化 |
| 索引作为 key | 仅在列表静态且不重新排序时使用 |
| 性能优化 | 使用 React.memo 和虚拟列表 |
| 常见陷阱 | 索引 key、key 不唯一、key 不传递 |
核心要点:
- 始终为列表项提供稳定的 key
- 优先使用数据中的唯一 ID
- 避免使用随机数作为 key
- 大数据量考虑虚拟化
更多推荐


所有评论(0)