React渲染大量JSX元素数组性能优化及虚拟滚动实现方案咨询
性能优化方案
一、现有代码问题修复(立即可见性能提升)
- 移除state中存储JSX元素的错误实践:state仅存储原始业务数据,不要存储已经渲染好的JSX节点,你的当前实现每次SSE更新都会全量替换JSX数组,触发所有卡片重渲染,性能损耗极高。
- 优化SSE更新逻辑:不要在父层遍历所有卡片查找匹配项,将SSE推送的 payload 按imei为key存储为Map结构,每个
DetailCard组件内部独立读取对应自身imei的最新数据,仅触发单卡片重渲染。 - 完善
DetailCard的memo比较逻辑:给memo传入第二个参数自定义浅比较规则,仅当卡片自身依赖的object1、object2属性发生变化时才触发重渲染,避免无效重绘。 - 优化循环计算逻辑:缓存
Object.keys(vehicleData)的结果,避免重复计算对象键值数组。
// 优化后的DetailCards示例 const DetailCards = memo(() => { const fetchDataContext = useContext(FetchDataContext) const { vehicleData, locationData, ssePayloadMap } = fetchDataContext // context里新增ssePayloadMap,key为imei,value为最新payload const vehicleKeys = useMemo(() => Object.keys(vehicleData), [vehicleData]) const initialLocationData = useMemo(() => Object.keys(locationData).length ? locationData : null, [locationData]) if (!initialLocationData) return null return vehicleKeys.map(imei => ( <DetailCard key={imei} object1={vehicleData[imei]} object2={ssePayloadMap.get(imei) || initialLocationData[imei]} /> )) }) // DetailCard的memo自定义比较 const DetailCard = memo(({object1, object2}) => { // 内部业务逻辑 }, (prevProps, nextProps) => { // 只有object1和object2的核心展示字段变化才重渲染,可根据业务调整比较粒度 return prevProps.object1.imei === nextProps.object1.imei && prevProps.object2.timestamp === nextProps.object2.timestamp })
二、虚拟化列表实现(大幅降低渲染量级,移动端提升明显)
虚拟化核心逻辑是仅渲染可视区域内的卡片,650个卡片实际可视区域最多同时展示10-20个,渲染量级直接降低90%以上,React17可以直接用react-window实现,适配你的PerfectScrollbar也很方便。
实现步骤
- 先确定你的卡片布局参数:每行展示的卡片数量
columnCount、单个卡片的宽度cardWidth、高度cardHeight、容器的宽高。 - 替换原有平铺渲染的逻辑为Grid虚拟化组件,示例代码如下:
// 改造Contestants组件 import { FixedSizeGrid as Grid } from 'react-window' const Contestants = memo(() => { const fetchDataContext = useContext(FetchDataContext) const { vehicleData, locationData, ssePayloadMap } = fetchDataContext const vehicleKeys = useMemo(() => Object.keys(vehicleData), [vehicleData]) const columnCount = 3 // 每行3张卡片,可根据响应式规则调整 const rowCount = Math.ceil(vehicleKeys.length / columnCount) const cardWidth = 280 // 按实际卡片宽度调整 const cardHeight = 360 // 按实际卡片高度调整,高度不固定可以换用VariableSizeGrid const Cell = ({ columnIndex, rowIndex, style }) => { const index = rowIndex * columnCount + columnIndex if (index >= vehicleKeys.length) return null const imei = vehicleKeys[index] return ( <div style={style}> <DetailCard key={imei} object1={vehicleData[imei]} object2={ssePayloadMap.get(imei) || locationData[imei]} /> </div> ) } return ( <div className={`contestants-container animate__animated animate__fadeIn ${classes.mainContainer}`}> <div className={`mx-0 my-0 ${classes.cardsContainer}`}> <PerfectScrollbar> <Grid columnCount={columnCount} columnWidth={cardWidth} rowCount={rowCount} rowHeight={cardHeight} height={600} // 滚动容器的高度,按实际业务场景调整 width="100%" className="d-flex flex-wrap align-items-center justify-content-evenly" > {Cell} </Grid> </PerfectScrollbar> </div> </div> ) })
补充优化
- 卡片内的图片添加懒加载属性,非可视区域的图片延迟加载
- 图表组件单独用
memo包裹,减少重渲染时的图表重绘开销 - 如果卡片高度不固定,替换
FixedSizeGrid为VariableSizeGrid,提前缓存每个卡片的高度即可
内容的提问来源于stack exchange,提问作者Wanda
相关产品推荐
相关产品推荐

