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 中使用 mapfunction 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 什么是 KeyKey 是 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}/li3.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 trth姓名/thth年龄/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 typecheckbox 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项建议使用虚拟列表库npminstallreact-windowimport { 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 componentul {items.map(item ( CSSTransition key{item.id} timeout{500} classNamesitem 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 typetext 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大数据量考虑虚拟化