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

如何在表格中高效渲染带onClick API调用的按钮,避免不必要请求

表格中带API调用的动作按钮优化方案

核心问题分析

你当前的两个方案存在的明确问题:

  • 方案1:ActionsMenu组件一渲染就执行useQuery,导致表格所有行初始化时就批量发起API请求,完全违背"点击才请求"的需求。
  • 方案2:依赖全局状态selectedPhoneNumber,所有行的ActionsMenu都会因该状态变化重渲染,不仅性能浪费,还会出现菜单需两次点击才能显示的异常。

解决方案

1. 让API请求仅在菜单打开时触发

修改ActionsMenu,将查询触发条件绑定到菜单显示状态,仅当菜单被打开时才发起API请求,同时用React.memo缓存组件避免无意义重渲染:

import { useState, memo } from 'react';
import { useQuery } from '@tanstack/react-query';
import api from '@/utils/api';
import { DotsThreeVertical } from '@/components/icons';

// 用memo包裹,仅当phoneNumber变化时重渲染
const ActionsMenu = memo(({ phoneNumber }: { phoneNumber: string }) => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);
  
  // 仅当菜单打开时启用查询,避免初始化请求
  const lineInfoQuery = api.lines.getActions.useQuery(
    { phoneNumber },
    {
      enabled: isMenuOpen,
      staleTime: 5 * 60 * 1000, // 缓存5分钟,避免重复请求
    }
  );

  if (lineInfoQuery.isLoading) {
    return (
      <>
        <div tabIndex={0} onClick={() => setIsMenuOpen(!isMenuOpen)}>
          <button className="btn btn-accent btn-sm btn-square">
            <DotsThreeVertical size={24} />
          </button>
        </div>
        {isMenuOpen && <div className="dropdown-content">加载中...</div>}
      </>
    );
  }

  const isError = lineInfoQuery.isError || !lineInfoQuery.data || typeof lineInfoQuery.data === 'string';
  if (isError) {
    return (
      <>
        <div tabIndex={0} onClick={() => setIsMenuOpen(!isMenuOpen)}>
          <button className="btn btn-accent btn-sm btn-square">
            <DotsThreeVertical size={24} />
          </button>
        </div>
        {isMenuOpen && <div className="dropdown-content">加载失败</div>}
      </>
    );
  }

  const lineInfo = lineInfoQuery.data as LineInfoResponse;
  const actions = [
    { id: 'swap', available: lineInfo.can_sim_swap, label: 'SIM更换' },
    { id: 'refresh', available: lineInfo.can_refresh, label: '刷新' },
    { id: 'update', available: lineInfo.can_update, label: '更新' },
  ];

  return (
    <>
      <div tabIndex={0} onClick={() => setIsMenuOpen(!isMenuOpen)}>
        <button className="btn btn-accent btn-sm btn-square">
          <DotsThreeVertical size={24} />
        </button>
      </div>
      {isMenuOpen && (
        <div className="dropdown dropdown-top dropdown-content">
          {actions.filter(a => a.available).map(action => (
            <button key={action.id} className="dropdown-item">
              {action.label}
            </button>
          ))}
        </div>
      )}
    </>
  );
});

export default ActionsMenu;

2. 优化表格列的渲染逻辑

在表格列定义中直接使用独立状态的ActionsMenu,无需全局状态,每个行的菜单独立管理自身状态:

const columns = [
  columnHelper.accessor('phoneNumber', {
    header: '电话号码',
    cell: (info) => formatPhoneNumber(info.getValue()),
  }),
  // 优化后的操作列
  columnHelper.display({
    header: '操作',
    id: 'actions',
    cell: (info) => (
      <div className="dropdown">
        <ActionsMenu phoneNumber={info.row.original.phoneNumber} />
      </div>
    ),
  }),
  columnHelper.accessor('simNumber', {
    header: 'SIM号码',
    cell: (info) => info.getValue(),
  }),
  /* ...更多列 */
];

3. 额外优化:避免表格列配置重复创建

用useMemo缓存列配置,避免组件每次渲染时重新创建列对象,进一步减少表格的重渲染触发:

import { useMemo } from 'react';

// 用useMemo缓存columns,依赖项为空数组确保仅创建一次
const columns = useMemo(() => [
  columnHelper.accessor('phoneNumber', {
    header: '电话号码',
    cell: (info) => formatPhoneNumber(info.getValue()),
  }),
  columnHelper.display({
    header: '操作',
    id: 'actions',
    cell: (info) => (
      <div className="dropdown">
        <ActionsMenu phoneNumber={info.row.original.phoneNumber} />
      </div>
    ),
  }),
  columnHelper.accessor('simNumber', {
    header: 'SIM号码',
    cell: (info) => info.getValue(),
  }),
  /* ...更多列 */
], []);

关键原理说明

  • 延迟请求触发:通过useQuery的enabled选项,严格控制API请求时机,仅在用户打开菜单时发起。
  • 独立状态隔离:每个ActionsMenu内部管理自身的打开状态,不依赖全局状态,避免一行操作导致全表重渲染。
  • 组件缓存优化:React.memo和useMemo的组合使用,最大程度减少不必要的组件渲染和对象创建。

内容的提问来源于stack exchange,提问作者Mendy Landa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 18:28:19