React渲染耗时过长:勾选Checkbox卡顿问题排查
点击Checkbox响应缓慢问题排查与优化
我页面仅遍历100条文档数据,使用封装的ActionsAccess组件处理条件渲染逻辑,但点击Checkbox时耗时极久。现在纠结是否要放弃封装组件,改用可读性较差的内联条件渲染?
主组件相关代码
const onClickCheckbox = (order: OrderProps) => { const _ordersToExport = [...ordersToExport]; const index = _ordersToExport.map(({ order }) => order.id).indexOf(order.order.id); if (index === -1) { _ordersToExport.push(order); } else { _ordersToExport.splice(index, 1); } setOrdersToExport(_ordersToExport); }; return ( <> {orders.map(({ order, ...rest }, index) => { return ( <div className="booking-details" key={order.id}> <Grid container className="bg-dark" mt={2} alignItems="center"> <Grid md={6} className="text-left" item> <Checkbox id="id1" checked={ordersToExport?.map(({ order }) => order.id).includes(order.id)} className="white" onChange={() => onClickCheckbox({ order, ...rest })} /> </Grid> <Grid md={6} className="text-right" item> <ButtonGroup variant="text" aria-label="button group" className="white-btn-group"> <> <ActionsAccess btnType="Accept" status={order.status}> <Tooltip title="Accept" placement="top" arrow> <Button size="large" onClick={() => updateOrder(order, 2)}> <CheckIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="Accept and open document" status={order.status}> <Tooltip title="Accept and open document" placement="top" arrow> <Button size="large" onClick={() => onClickAcceptOpen(order, 2)}> <TaskIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="Accept and add note" status={order.status}> <Tooltip title="Accept and add note" placement="top" arrow> <Button size="large" onClick={() => handleChange(`panel${`${index}-note`}`)}> <SmsIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="B-Scan" status={order.status}> <Tooltip title="B-Scan" placement="top" arrow> <Button size="large" onClick={() => handleChange(`panel${`${index}-bscan`}`)}> <SystemUpdateAlt /> </Button> </Tooltip> </ActionsAccess> </> <ActionsAccess btnType="View document" status={order.status}> <Tooltip title="View document" placement="top" arrow> <Button size="large" onClick={() => window.open(`/mybooking/overview-today/view-pdf?id=${order.id}`)}> <DescriptionIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="Delete order" status={order.status}> <Tooltip title="Delete order" placement="top" arrow onClick={() => handleChange(`panel${`${index}-delete`}`)}> <Button size="large"> <DeleteIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="Reject order" status={order.status}> <Tooltip title="Reject order" placement="top" arrow onClick={() => handleChange(`panel${`${index}-reason`}`)}> <Button size="large"> <CancelIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="View tracking" status={order.status}> <Tooltip title="View tracking" placement="top" arrow> <Button size="large" onClick={() => { navigate(`/event-tracking?id=${order.id}`); setCount(count + 1); }} > <ZoomInIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="Quick edit" status={order.status}> <Tooltip title="Quick edit" placement="top" arrow> <Button size="large" onClick={() => onClickQuickEdit({ order, ...rest })}> <FlashOnIcon /> </Button> </Tooltip> </ActionsAccess> <ActionsAccess btnType="Modify order" status={order.status}> <Tooltip title="Modify order" placement="top" arrow> <Button size="large" onClick={() => navigate(`/edit-order?id=${order.id}`)}> <EditIcon /> </Button> </Tooltip> </ActionsAccess> </ButtonGroup> </Grid> </Grid> </div> ); })} </> );
ActionsAccess组件原始代码
const enums: Enums = { 1: [ "Accept", "Accept and open document", "Accept and add note", "View document", "Delete order", "Reject order", "View tracking", "Modify order", ], 2: ["View document", "Delete order", "Reject order", "View tracking"], 3: ["View document", "Delete order", "View tracking"], 4: ["View document", "Delete order", "View tracking", "Modify order", "Quick edit"], 8: ["View document", "Delete order", "View tracking", "Modify order", "Quick edit"], 5: ["View document", "View tracking"], 9: ["View document", "Delete order", "View tracking", "Modify order"] }; export default function ActionsAccess({ children, status, btnType }: props) { const feature = enums[status]; return feature?.indexOf(btnType) !== -1 ? children : null; }
尝试的优化代码(未解决问题)
export default function ActionsAccess({ children, status, btnType }: props) { const memoChildren = React.useMemo(() => { const feature = enums[status]; return feature?.indexOf(btnType) !== -1 ? children : null; }, [status]); return memoChildren; }
问题分析与解决方案
核心性能瓶颈
- 重复遍历数组的低效操作:点击Checkbox时,
onClickCheckbox里用map+indexOf遍历数组;渲染时Checkbox的checked属性又用map+includes遍历,100条数据每次操作都要多次全量遍历,累积开销极大。 - DOM ID重复:所有Checkbox都用
id="id1",违反DOM唯一ID规则,会导致浏览器行为异常,也可能间接影响渲染性能。 - 组件缓存不完整:给
ActionsAccess加了useMemo但依赖不全(缺btnType),且未给组件本身加React.memo,父组件重渲染时该组件会全部重新执行,缓存无效。
具体优化步骤
1. 优化选中状态的查找逻辑
用Set存储已选中ID,把O(n)的查找改成O(1):
// 预处理选中ID集合,缓存起来 const selectedOrderIds = React.useMemo(() => new Set(ordersToExport.map(item => item.order.id)), [ordersToExport]); const onClickCheckbox = ({ order, ...rest }: OrderProps) => { const orderId = order.id; setOrdersToExport(prev => { if (selectedOrderIds.has(orderId)) { return prev.filter(item => item.order.id !== orderId); } else { return [...prev, { order, ...rest }]; } }); };
2. 修复Checkbox的ID和checked属性
<Checkbox id={`order-checkbox-${order.id}`} // 唯一ID checked={selectedOrderIds.has(order.id)} // 直接用Set判断,避免遍历 className="white" onChange={() => onClickCheckbox({ order, ...rest })} />
3. 正确优化ActionsAccess组件
把枚举转换成Set提升查找速度,同时用React.memo缓存组件:
import React from 'react'; const enums: Enums = { // 保持原枚举内容不变 }; // 把数组转换成Set,提升查找效率 const statusToActions = Object.fromEntries( Object.entries(enums).map(([status, actions]) => [status, new Set(actions)]) ); const ActionsAccess = React.memo(({ children, status, btnType }: props) => { const allowed = React.useMemo(() => { const actions = statusToActions[status]; return actions?.has(btnType) ?? false; }, [status, btnType]); return allowed ? children : null; }); export default ActionsAccess;
4. 缓存列表项组件
把map里的内容抽成独立组件,用React.memo包裹,避免全量重渲染:
const BookingItem = React.memo(({ order, rest, selectedOrderIds, onClickCheckbox }) => { return ( <div className="booking-details"> {/* 原有的Grid、Checkbox、ButtonGroup等内容 */} </div> ); }); // 主组件渲染部分 {orders.map(({ order, ...rest }) => ( <BookingItem key={order.id} order={order} rest={rest} selectedOrderIds={selectedOrderIds} onClickCheckbox={onClickCheckbox} /> ))}
总结
不用放弃封装组件,问题出在重复遍历、DOM ID错误、组件缓存不完整这几个点上,按上述步骤优化后,性能会明显提升。
内容的提问来源于stack exchange,提问作者Profer
相关产品推荐
相关产品推荐

