修改单个state后页面全量重载,大量图表重复加载如何解决?
问题描述
- 开发包含大量图表的页面,使用Material UI菜单实现不同图表组的切换功能,点击MenuItem时修改
part状态渲染对应组件数组 - 核心问题:每次点击菜单按钮后所有图表都会触发一次快速加载,图表数量较多时严重影响页面使用体验
- 调试情况:所有state均按需求正常赋值,useEffects也仅在页面初始化时执行一次,无多余的加载逻辑
- 效果演示:

现有代码
渲染代码
<> <div className={classes.actionBar}> <SelectEstimatedMonthForm /> </div> <div> <Card> <div> <h1>Gráficos de DRE</h1> <Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick} > Open Menu </Button> <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose} > <MenuItem onClick={handleClose}>Todos Gráficos</MenuItem> <MenuItem onClick={handleClose}> Gráficos DRE Orçamento Evolução </MenuItem> <MenuItem onClick={handleClose}> Gráficos de Evolução Parte 2 </MenuItem> <MenuItem onClick={handleClose}> Gráficos de Representatividade </MenuItem> </Menu> </div> <div> {chartDataPart?.part1 && part === 1 && values && ( <div> {chartDataPart?.part1?.map((data) => { if (data?.type === "bar") { return ( <div> {data?.labels && ( <ChartBar data={data} text={data?.text} noLabel={data?.noLabel} stacked={data?.stacked} newPlugin={data?.customPlugin} /> )} </div> ); } else if (data?.type === "pie") { return ( <div> {data?.labels && ( <ChartPie text={data?.text} data={data} /> )} </div> ); } else if (data?.type === "doughnut") { return ( <div> {data?.labels && ( <ChartDoughnut text={data?.text} data={data} /> )} </div> ); } else { return ( <div> <h1>{data?.text} esta sem type</h1> </div> ); } })} </div> )} {chartDataPart?.part2 && part === 2 && values && ( <div> {chartDataPart?.part2?.map((data) => { if (data?.type === "bar") { return ( <div> {data?.labels && ( <ChartBar data={data} text={data?.text} noLabel={data?.noLabel} stacked={data?.stacked} newPlugin={data?.customPlugin} /> )} </div> ); } else if (data?.type === "pie") { return ( <div> {data?.labels && ( <ChartPie text={data?.text} data={data} /> )} </div> ); } else if (data?.type === "doughnut") { return ( <div> {data?.labels && ( <ChartDoughnut text={data?.text} data={data} /> )} </div> ); } else { return ( <div> <h1>{data?.text} esta sem type</h1> </div> ); } })} </div> )} {chartDataPart?.part3 && part === 3 && values && ( <div> {chartDataPart?.part3?.map((data) => { if (data?.type === "bar") { return ( <div> {data?.labels && ( <ChartBar data={data} text={data?.text} noLabel={data?.noLabel} stacked={data?.stacked} newPlugin={data?.customPlugin} /> )} </div> ); } else if (data?.type === "pie") { return ( <div> {data?.labels && ( <ChartPie text={data?.text} data={data} /> )} </div> ); } else if (data?.type === "doughnut") { return ( <div> {data?.labels && ( <ChartDoughnut text={data?.text} data={data} /> )} </div> ); } else { return ( <div> <h1>{data?.text} esta sem type</h1> </div> ); } })} </div> )} {chartData && part === 0 && values && ( <div> {chartData?.map((data) => { if (data?.type === "bar") { return ( <div> {data?.labels && ( <ChartBar data={data} text={data?.text} noLabel={data?.noLabel} stacked={data?.stacked} newPlugin={data?.customPlugin} /> )} </div> ); } else if (data?.type === "pie") { return ( <div> {data?.labels && ( <ChartPie text={data?.text} data={data} /> )} </div> ); } else if (data?.type === "doughnut") { return ( <div> {data?.labels && ( <ChartDoughnut text={data?.text} data={data} /> )} </div> ); } else { return ( <div> <h1>{data?.text} esta sem type</h1> </div> ); } })} </div> )} </div> </Card> </div> </>
副作用逻辑代码
useEffect(() => { const fetchDreData = async () => { try { const res = await http.get( `call1` ); const data = await res.data; const final = data.data; setEstimatedMonths(final); } catch (e) { console.log("error", e); } }; const fetchProductsData = async () => { try { const res = await http.get( `call2` ); const data = await res.data; const final = data.data; setEstimatedProducts(final); } catch (e) { console.log("error", e); } }; fetchDreData(); fetchProductsData(); }, [estimatedMonthContext.monthId]); useEffect(() => { if (estimatedProducts && estimatedMonths) { flatData(); } }, [estimatedProducts, estimatedMonths]); useLayoutEffect(() => { chart(); }, [values]);
优化方案
核心问题根因
当前你使用条件渲染控制不同图表组的显示隐藏,每次part变化时,旧图表组的DOM会被完全卸载,新图表组的DOM会重新挂载,所有图表都会重新走初始化逻辑,因此触发加载动画。
具体优化点
1. 渲染逻辑修改
将条件卸载改为CSS隐藏,保留DOM实例,切换时无需重新初始化图表:
// 替换原有条件渲染逻辑 <div style={{display: part === 1 ? 'block' : 'none'}}> {chartDataPart?.part1 && values && /* 原part1的图表渲染逻辑 */} </div> <div style={{display: part === 2 ? 'block' : 'none'}}> {chartDataPart?.part2 && values && /* 原part2的图表渲染逻辑 */} </div> <div style={{display: part === 3 ? 'block' : 'none'}}> {chartDataPart?.part3 && values && /* 原part3的图表渲染逻辑 */} </div> <div style={{display: part === 0 ? 'block' : 'none'}}> {chartData && values && /* 原part0的图表渲染逻辑 */} </div>
2. 冗余代码抽离
4组图表的渲染逻辑完全一致,可以抽成通用组件减少重复代码:
// 抽通用图表组渲染组件 const ChartGroup = ({dataList}) => { return dataList?.map((data, idx) => { if (!data?.labels) return null switch(data.type) { case 'bar': return <ChartBar key={idx} data={data} text={data.text} noLabel={data.noLabel} stacked={data.stacked} newPlugin={data.customPlugin} /> case 'pie': return <ChartPie key={idx} text={data.text} data={data} /> case 'doughnut': return <ChartDoughnut key={idx} text={data.text} data={data} /> default: return <h1 key={idx}>{data?.text} esta sem type</h1> } }) } // 调用时直接传入对应分组数据即可 <div style={{display: part === 1 ? 'block' : 'none'}}> {chartDataPart?.part1 && values && <ChartGroup dataList={chartDataPart.part1} />} </div>
3. 性能兜底优化
- 给所有
map渲染的元素加唯一key,优先用数据内的唯一ID而非索引,减少不必要的重渲染 - 给图表组件包裹
React.memo,仅当props变化时才重新渲染 - 图表数量极多时可增加虚拟滚动,仅渲染可视区域内的图表
内容的提问来源于stack exchange,提问作者zhyp
相关产品推荐
相关产品推荐

