第九章:列表渲染
9.1 基础列表渲染
使用 JavaScript 的 map 方法将数组转换为元素列表。
function NumberList({ numbers }) { const listItems = numbers.map((number) => <li>{number}</li> ); return <ul>{listItems}</ul>; } // 更简洁的写法 function NumberList({ numbers }) { return ( <ul> {numbers.map(number => <li>{number}</li>)} </ul> ); } // 使用 const numbers = [1, 2, 3, 4, 5]; <NumberList numbers={numbers} />
9.2 Key 的重要性
Key 帮助 React 识别哪些元素改变了、添加了或删除了。
function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul>; ); }
选择 Key 的最佳实践:
- 使用数据中的唯一标识(如 ID)
- 避免使用数组索引(除非列表不会重新排序)
- 保持 Key 在同一兄弟元素中唯一
为什么不能用索引作为 Key:
// 错误示例 function List({ items }) { return ( <ul> {items.map((item, index) => ( <input key={index} value={item.value} /> ))} </ul>; ); } // 问题:删除第一个元素后,所有元素的 key 都变了 // 导致 React 重新渲染所有输入框,丢失焦点状态
9.3 提取列表组件
function ListItem({ item }) { return <li>{item.text}</li>; } function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <ListItem key={todo.id} item={todo} /> ))} </ul>; ); }
注意:key 应该放在数组上下文的元素上,而不是组件内部。
9.4 嵌套列表
function CategoryList({ categories }) { return ( <div> {categories.map(category => ( <div key={category.id}> <h2>{category.name}</h2> <ul> {category.items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> </div> ))} </div>; ); }
9.5 列表过滤和排序
过滤列表:
function FilteredList({ items, filterText }) { const filtered = items.filter(item => item.name.toLowerCase().includes(filterText.toLowerCase()) ); return ( <ul> {filtered.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul>; ); }
排序列表:
function SortedList({ items, sortBy }) { const sorted = [...items].sort((a, b) => { if (sortBy === 'name') { return a.name.localeCompare(b.name); } if (sortBy === 'date') { return new Date(b.date) - new Date(a.date); } return 0; }); return ( <ul> {sorted.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul>; ); }
9.6 虚拟列表
当列表项很多时,使用虚拟列表只渲染可视区域的内容。
使用 react-window:
npm install react-window
import { FixedSizeList as List } from 'react-window'; function VirtualList({ items }) { const Row = ({ index, style }) => ( <div style={style}> {items[index].name} </div>; ); return ( <List height={500} itemCount={items.length} itemSize={35} > {Row} </List>; ); }
使用 react-virtualized:
npm install react-virtualized
import { List } from 'react-virtualized'; function VirtualizedList({ items }) { const rowRenderer = ({ key, index, style }) => ( <div key={key} style={style}> {items[index].name} </div>; ); return ( <List width={300} height={500} rowCount={items.length} rowHeight={50} rowRenderer={rowRenderer} />; ); }
9.7 无限滚动
import { useState, useEffect, useRef, useCallback } from 'react'; function InfiniteList() { const [items, setItems] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const observer = useRef(); const lastItemRef = useCallback(node => { if (loading) return; if (observer.current) observer.current.disconnect(); observer.current = new IntersectionObserver(entries => { if (entries[0].isIntersecting) { setPage(prevPage => prevPage + 1); } }); if (node) observer.current.observe(node); }, [loading]); useEffect(() => { setLoading(true); fetchItems(page).then(newItems => { setItems(prev => [...prev, ...newItems]); setLoading(false); }); }, [page]); return ( <div> {items.map((item, index) => ( <div key={item.id} ref={index === items.length - 1 ? lastItemRef : null} > {item.name} </div>; ))} {loading && <p>加载中...</p>} </div>; ); }
9.8 列表动画
使用 react-flip-toolkit:
npm install react-flip-toolkit
import { Flipper, Flipped } from 'react-flip-toolkit'; function AnimatedList({ items }) { return ( <Flipper flipKey={items.map(i => i.id).join(',')}> <ul> {items.map(item => ( <Flipped key={item.id} flipId={item.id}> <li>{item.name}</li> </Flipped>; ))} </ul>; </Flipper>; ); }
使用 framer-motion:
npm install framer-motion
import { motion, AnimatePresence } from 'framer-motion'; function AnimatedList({ items, onRemove }) { return ( <AnimatePresence> {items.map(item => ( <motion.li key={item.id} initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, x: -100 }} onClick={() => onRemove(item.id)} > {item.name} </motion.li>; ))} </AnimatePresence>; ); }
9.9 总结
本章介绍了列表渲染的各种技术:
- 基础列表渲染
- Key 的重要性和最佳实践
- 列表过滤和排序
- 虚拟列表优化
- 无限滚动
- 列表动画
处理列表是 React 开发的常见场景,掌握这些技巧对于构建高性能应用非常重要。