React中Accordion展开时Map渲染表格加载缓慢问题及优化需求
解决方案:优化Accordion展开时的表格渲染性能
一、核心性能问题定位
你遇到的慢渲染问题根源在于setIndex触发父组件重渲染时,导致所有Accordion及内部表格都重复渲染,再加上key使用不当、函数引用不稳定等问题放大了开销。以下是具体优化步骤:
二、具体优化措施
1. 修复列表项的key值
不要用数组索引i作为TableRow的key,改用数据的唯一标识(比如item.resource_id)。索引作为key会导致React在列表更新时错误复用组件,增加不必要的DOM操作:
<TableRow key={item.resource_id}> {/* 替换原有的key={i} */} <TableCell>{item.resource_id}</TableCell> <TableCell>{item.resource_type}</TableCell> <TableCell align="center">{item.observerd_at}</TableCell> <TableCell align="center">{formatCurrency(item.saving)}</TableCell> </TableRow>
2. 稳定handleChange函数引用
当前每次渲染都会创建新的handleChange函数,导致Accordion的onChange属性频繁变化,触发无意义重渲染。用useCallback包裹函数,保持引用稳定:
import { useCallback } from 'react'; // 替换原handleChange定义 const handleChange = useCallback((accountId) => (event, isExpanded) => { setIndex(isExpanded ? accountId : undefined); }, [setIndex]);
3. 缩小重渲染范围
- 用
React.memo包裹AccountEvents组件,确保只有当data属性变化时才重渲染:
import { memo } from 'react'; const AccountEvents = memo(({ data }) => { // 原表格组件代码 });
- 将单个Accordion拆成独立子组件,避免父组件状态变化时所有Accordion都重渲染:
const AccountAccordion = memo(({ account, recommendations, handleChange, currentIndex }) => { return ( <Box sx={{ py: 2 }}> <Accordion expanded={currentIndex === account.account_id} onChange={handleChange(account.account_id)} > <AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`panel-${account.account_id}-content`} id={`panel-${account.account_id}-header`} > <div className="row"> <Typography align="left"><b>Account: </b>{account.account_name}</Typography> <Typography align="left"><b>Potential Monthly Savings: </b>{formatCurrency(account.total_saving)}</Typography> </div> </AccordionSummary> <AccordionDetails> <div className="row"> <Divider color="grey" /> <Box sx={{ py: 2 }}> <Typography align="left"> <b>Account #: </b>{account.account_id} </Typography> </Box> <Divider color="grey" /> <Box sx={{ py: 2 }}> <AccountEvents data={recommendations} /> </Box> </div> </AccordionDetails> </Accordion> </Box> ); });
然后父组件中调用:
{props.accounts ? props.accounts.map((account) => ( <AccountAccordion key={account.account_id} account={account} recommendations={recommendations} handleChange={handleChange} currentIndex={index} /> )) : <Typography>No accounts to display</Typography>}
4. 缓存格式化计算结果
如果formatCurrency有计算开销,用useMemo缓存每个项的格式化值,避免每次渲染重复计算:
import { useMemo } from 'react'; const AccountEvents = memo(({ data }) => { // 提前缓存所有格式化后的值 const formattedData = useMemo(() => { return data?.map(item => ({ ...item, formattedSaving: formatCurrency(item.saving) })) || []; }, [data]); return ( <TableContainer component={Paper}> <Table size="small" aria-label="purchases" sx={{ p: 2 }}> {/* 表头代码不变 */} <TableBody> {formattedData.map((item) => ( <TableRow key={item.resource_id}> {/* ...其他单元格 */} <TableCell align="center">{item.formattedSaving}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ); });
三、加载状态处理
如果优化后仍有渲染延迟,可以通过DOM渲染完成的时机来控制加载状态:
import { memo, useState, useEffect, useRef } from 'react'; import CircularProgress from '@mui/material/CircularProgress'; const AccountEvents = memo(({ data }) => { const [isLoading, setIsLoading] = useState(true); const tableRef = useRef(null); useEffect(() => { // 表格DOM渲染完成后关闭加载状态 if (tableRef.current) { setIsLoading(false); } }, [data]); if (!data) return <Typography>No data available</Typography>; return ( <> {isLoading && ( <div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}> <CircularProgress /> </div> )} <TableContainer component={Paper} ref={tableRef} style={{ display: isLoading ? 'none' : 'block' }} > {/* 原表格代码 */} </TableContainer> </> ); });
内容的提问来源于stack exchange,提问作者OneTwo
相关产品推荐
相关产品推荐

