You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

问题分析与解决方案

核心性能瓶颈

  1. 重复遍历数组的低效操作:点击Checkbox时,onClickCheckbox里用map+indexOf遍历数组;渲染时Checkbox的checked属性又用map+includes遍历,100条数据每次操作都要多次全量遍历,累积开销极大。
  2. DOM ID重复:所有Checkbox都用id="id1",违反DOM唯一ID规则,会导致浏览器行为异常,也可能间接影响渲染性能。
  3. 组件缓存不完整:给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:07:02