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

React useEffect重复执行两次致数组重复添加元素问题求助

问题原因分析

你遇到的操作列重复添加问题,主要有两个核心原因:

  1. React 18 StrictMode 的副作用重复执行
    在 React 18+ 的开发模式下,StrictMode 会刻意重复调用一次无依赖项的 useEffect 回调(以及其他一些生命周期方法)。这么做是为了帮你检测那些重复执行会引发问题的副作用代码——而你的 actionColumn 函数直接修改了传入的 columns 数组,所以两次执行就会导致操作列被添加两次。

  2. 直接修改 Props 数组的反模式
    你通过 columns.push() 直接修改了父组件传递过来的 columns props。React 中 props 是只读的,直接修改它不仅违反单向数据流原则,还可能导致父组件的状态(如果 columns 是父组件的 state)被意外篡改,进而引发更多不可预测的渲染问题。

解决方案

正确的做法是避免直接修改 props,而是通过 useMemo 在组件渲染时计算出最终的列数组,这样既符合 React 的状态管理范式,也能避免重复添加的问题。

修改后的组件代码如下:

import { useMemo } from 'react';

const RelevoTable = ({ 
  records, 
  columns, 
  hasPagination = true, 
  sorted, 
  hasSearch = false, 
  exportCsv = false, 
  bordered = false, 
  hasToogle = false, 
  isCondensed = false, 
  actionConfig = null, 
}) => {
  // 使用 useMemo 计算最终的列数组,避免重复修改和不必要的计算
  const finalColumns = useMemo(() => {
    // 如果没有 actionConfig,直接返回原列数组的副本
    if (!actionConfig) return [...columns];

    // 创建原列数组的副本,避免修改原 props
    const updatedColumns = [...columns];
    updatedColumns.push({
      dataField: 'actions',
      text: actionConfig.text,
      sort: false,
      formatter: (row, cell) => {
        return (
          <UncontrolledDropdown>
            <DropdownToggle tag="button" className="btn btn-link arrow-none card-drop p-0">
              <i className="mdi mdi-dots-horizontal" style={{ fontSize: 25, color: "#6c757d" }}></i>
            </DropdownToggle>
            <DropdownMenu right>
              {/* 添加 key 属性,避免 React 警告 */}
              {actionConfig.itens.map((e, index) => (
                <DropdownItem key={index} onClick={() => e.myEvent(cell)}>
                  <i className={e.icon}></i>{e.label}
                </DropdownItem>
              ))}
            </DropdownMenu>
          </UncontrolledDropdown>
        );
      }
    });

    return updatedColumns;
  }, [columns, actionConfig]); // 依赖项:当 columns 或 actionConfig 变化时重新计算

  return (
    <>
      <Card>
        <CardBody>
          <ToolkitProvider 
            bootstrap4 
            keyField="id" 
            data={records} 
            columns={finalColumns} {/* 使用计算后的 finalColumns */}
            search={hasSearch} 
            exportCSV={{ onlyExportFiltered: true, exportAll: false }} 
            columnToggle={hasToogle}
          >
            {props => (
              <React.Fragment>
                <Row>
                  {hasToogle ? (
                    <div>
                      <CustomToggleList {...props.columnToggleProps} />
                    </div>
                  ) : null}
                  {hasSearch ? (
                    <Col>
                      <SearchBar {...props.searchProps} />
                    </Col>
                  ) : null}
                  {exportCsv ? (
                    <Col className="text-right">
                      <ExportCSVButton {...props.csvProps} className="btn btn-primary">
                        Export CSV
                      </ExportCSVButton>
                    </Col>
                  ) : null}
                </Row>
                <BootstrapTable 
                  condensed={isCondensed} 
                  {...props.baseProps} 
                  bordered={bordered} 
                  defaultSorted={sorted} 
                  pagination={hasPagination ? paginationFactory(paginationOptions) : null} 
                  wrapperClasses="table-responsive" 
                />
              </React.Fragment>
            )}
          </ToolkitProvider>
        </CardBody>
      </Card>
    </>
  );
}

export default RelevoTable;
关键修改点说明
  • 使用 useMemo 计算列数组:useMemo 会缓存计算结果,只有当依赖项(columns 或 actionConfig)变化时才会重新计算,避免了不必要的重复操作。
  • 创建数组副本:通过 [...columns] 创建原列数组的浅拷贝,永远不直接修改 props,遵循 React 的单向数据流原则。
  • 添加 key 属性:给 DropdownItem 添加了 key 属性,解决了 React 列表渲染缺少唯一标识的警告。
  • 移除 useEffect:不再依赖副作用来修改列数组,而是在渲染阶段直接计算最终结果,更符合 React 的声明式编程理念。

这样修改后,无论是开发环境的 StrictMode 还是生产环境,都不会再出现操作列重复添加的问题,同时代码也更健壮、可维护。

内容的提问来源于stack exchange,提问作者Guilherme Prado

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 17:57:43