React useEffect重复执行两次致数组重复添加元素问题求助
问题原因分析
你遇到的操作列重复添加问题,主要有两个核心原因:
React 18 StrictMode 的副作用重复执行
在 React 18+ 的开发模式下,StrictMode会刻意重复调用一次无依赖项的useEffect回调(以及其他一些生命周期方法)。这么做是为了帮你检测那些重复执行会引发问题的副作用代码——而你的actionColumn函数直接修改了传入的columns数组,所以两次执行就会导致操作列被添加两次。直接修改 Props 数组的反模式
你通过columns.push()直接修改了父组件传递过来的columnsprops。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
相关产品推荐
相关产品推荐

