04-性能优化与最佳实践——04. 列表渲染优化 - key 与虚拟化
2026/6/25 12:32:36 网站建设 项目流程

04. 列表渲染优化 - key 与虚拟化

一、5W1H 概述

维度内容
What使用稳定的 key 标识列表项,使用虚拟列表处理大数据
Why避免不必要的 DOM 操作,处理大量数据时的性能问题
When动态列表、大数据量渲染(1000+ 条)
Where列表渲染组件中
Who处理列表数据的开发者
How使用唯一 ID 作为 key,使用 react-window 实现虚拟列表

二、What - 什么是 key 和虚拟化?

2.1 key 的作用

key 是 React 用来识别列表项的唯一标识符,帮助 React 确定哪些元素被添加、更改或删除。

2.2 虚拟列表

虚拟列表只渲染可视区域内的元素,滚动时动态替换,从而处理大量数据。


三、Why - 为什么需要这些优化?

3.1 key 的重要性

没有 key 时: [1,2,3] → [1,2,3,4] React 会重新渲染整个列表 有 key 时: [1,2,3] → [1,2,3,4] React 只添加新的第 4 项

3.2 虚拟列表的必要性

渲染 10000 条数据时,直接渲染所有 DOM 节点会导致性能问题。

// ❌ 渲染 10000 条数据,性能差 function BadList({ items }) { return ( <ul> {items.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> ); } // ✅ 使用虚拟列表 import { FixedSizeList as List } from 'react-window'; function GoodList({ items }) { const Row = ({ index, style }) => ( <div style={style}>{items[index].text}</div> ); return ( <List height={400} itemCount={items.length} itemSize={35} width="100%" > {Row} </List> ); }

四、When - 何时使用?

场景优化方式
动态列表稳定 key
列表顺序可能变化稳定 key(不要用索引)
大数据量(1000+)虚拟列表
无限滚动虚拟列表 + 懒加载
表格数据虚拟表格

五、Where - 在哪里使用?

  • 列表渲染组件中
  • 使用 key 属性
  • 使用虚拟列表库

六、Who - 谁需要使用?

所有处理列表数据的 React 开发者。


七、How - 如何使用?

7.1 选择合适的 key

// ✅ 好的 Key:使用数据中的唯一 ID <li key={todo.id}>{todo.text}</li> // ✅ 可以:使用索引(仅在列表静态且不会重新排序时) <li key={index}>{todo.text}</li> // ❌ 坏的 Key:使用随机数 <li key={Math.random()}>{todo.text}</li>

7.2 key 的最佳实践

场景推荐 Key说明
数据库数据数据库 ID最稳定
本地生成数据uuid / nanoid保证唯一性
静态列表索引(谨慎)列表不变时可接受
动态列表唯一标识符必须稳定且唯一

7.3 使用索引作为 key 的问题

// ❌ 当列表顺序变化时会有问题 {todos.map((todo, index) => ( <TodoItem key={index} todo={todo} /> ))} // 问题: // 1. 删除第一项时,所有后续项的 key 都改变了 // 2. React 会重新渲染所有项,而不是只删除一项 // 3. 可能导致组件状态错乱

7.4 组合 key

// 当没有唯一 ID 时,可以组合多个字段 {items.map(item => ( <div key={`${item.type}-${item.id}`}> {item.value} </div> ))}

7.5 react-window 固定高度列表

npminstallreact-window
import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style, data }) => ( <div style={style}> {data[index]} - 第 {index + 1} 项 </div> ); function VirtualList({ items }) { return ( <List height={400} itemCount={items.length} itemSize={35} width="100%" itemData={items} > {Row} </List> ); }

7.6 react-window 可变高度列表

import { VariableSizeList as List } from 'react-window'; const rowHeights = new Array(1000).fill(50); const Row = ({ index, style, data }) => ( <div style={style}> 第 {index} 项 - 高度可变 </div> ); function VariableList({ items }) { const getItemSize = (index) => rowHeights[index]; return ( <List height={400} itemCount={items.length} itemSize={getItemSize} width="100%" > {Row} </List> ); }

7.7 react-window 网格布局

import { FixedSizeGrid as Grid } from 'react-window'; const Cell = ({ columnIndex, rowIndex, style, data }) => ( <div style={style}> 行 {rowIndex}, 列 {columnIndex} </div> ); function VirtualGrid() { return ( <Grid columnCount={3} columnWidth={200} height={400} rowCount={100} rowHeight={50} width={600} > {Cell} </Grid> ); }

7.8 无限滚动 + 虚拟列表

import { FixedSizeList as List } from 'react-window'; import InfiniteLoader from 'react-window-infinite-loader'; function InfiniteList() { const [items, setItems] = useState([]); const [hasNextPage, setHasNextPage] = useState(true); const loadMoreItems = async (startIndex, stopIndex) => { const newItems = await fetchItems(startIndex, stopIndex); setItems(prev => [...prev, ...newItems]); setHasNextPage(newItems.length > 0); }; const isItemLoaded = (index) => !hasNextPage || index < items.length; const Row = ({ index, style }) => ( <div style={style}> {isItemLoaded(index) ? items[index] : '加载中...'} </div> ); return ( <InfiniteLoader isItemLoaded={isItemLoaded} itemCount={items.length + 100} loadMoreItems={loadMoreItems} > {({ onItemsRendered, ref }) => ( <List height={400} itemCount={items.length} itemSize={35} onItemsRendered={onItemsRendered} ref={ref} width="100%" > {Row} </List> )} </InfiniteLoader> ); }

7.9 虚拟表格

import { FixedSizeList as List } from 'react-window'; function VirtualTable({ data }) { const Row = ({ index, style }) => { const row = data[index]; return ( <div style={style} className="table-row"> <div className="cell">{row.id}</div> <div className="cell">{row.name}</div> <div className="cell">{row.email}</div> </div> ); }; return ( <div> <div className="table-header"> <div className="cell">ID</div> <div className="cell">姓名</div> <div className="cell">邮箱</div> </div> <List height={400} itemCount={data.length} itemSize={35} width="100%" > {Row} </List> </div> ); }

7.10 性能优化技巧

// 1. 使用 React.memo 优化列表项 const Row = React.memo(({ index, style, data }) => { return <div style={style}>{data[index]}</div>; }); // 2. 避免内联函数 // ❌ 每次渲染创建新函数 <List rowRenderer={({ index, style }) => ( <div style={style}>{items[index]}</div> )} /> // ✅ 提取组件 const Row = ({ index, style, data }) => ( <div style={style}>{data[index]}</div> ); <List rowRenderer={Row} itemData={items} /> // 3. 设置 overscanCount <List overscanCount={5} // 预渲染上下各5行 // ... />

八、常见陷阱

8.1 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> ))}

8.2 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} /> ))}

8.3 虚拟列表容器高度

// ❌ 容器没有固定高度 <div> <List height="auto" ... /> {/* 不会工作 */} </div> // ✅ 容器必须有固定高度 <div style={{ height: '400px' }}> <List height={400} ... /> </div>

九、练习题

基础题

  1. 渲染一个列表,使用正确的 key
  2. 实现一个虚拟列表,处理 10000 条数据

进阶题

  1. 实现一个可拖拽排序的虚拟列表
  2. 实现一个无限滚动的虚拟列表

十、小结

要点说明
key唯一稳定,帮助 React 识别变化
索引作为 key仅在列表静态且不重新排序时使用
虚拟列表大数据量使用 react-window
性能优化React.memo、避免内联函数、overscanCount

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询