如何在表格中高效渲染带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
相关产品推荐
相关产品推荐

