React+Ant Design Table动态列更新后组件失效问题求助
问题场景
使用React搭配Ant Design Table时,首次渲染所有功能正常,但从第二次渲染开始,动态添加的Action列中ActionOuvrage组件无法交互,props无法同步更新,仿佛与主组件断开连接。
初始代码(存在问题)
const columns = moduleColumn.getColumns(columnName) columns[columns.length-1].title ==='Action' ? console.log("no need to add the column! ") : columns.push({ title: "Action", dataIndex: "id", key: "id", align: "center", render: (text, record, index) => { return ( <ActionOuvrage text={text} record={record} index={index} ouvrage={props.ouvrage} refData={props.refData} handleShownComponent={props.handleShownComponent} handleDataAction={props.handleDataAction} handleIdOuvrage={props.handleIdOuvrage} loadOuvrageData={loadOuvrageData} /> ) }, }) setTableData((prevState) => {return { ...prevState, data: fetchedData, columns: columns, total: count }});
修改后代码(解决问题)
const columns = moduleColumn.getColumns(columnName) columns[columns.length-1].title ==='Action' ? columns.pop() : console.log("no need to update the column! ") columns.push({ title: "Action", dataIndex: "id", key: "id", align: "center", render: (text, record, index) => { return ( <ActionOuvrage text={text} record={record} index={index} ouvrage={props.ouvrage} refData={props.refData} handleShownComponent={props.handleShownComponent} handleDataAction={props.handleDataAction} handleIdOuvrage={props.handleIdOuvrage} loadOuvrageData={loadOuvrageData} /> ) }, }) setTableData((prevState) => {return { ...prevState, data: fetchedData, columns: columns, total: count }});
已知moduleColumn是存储列配置的静态JS文件,因JavaScript的引用特性,向其数组添加的对象会永久保留直到页面刷新。
原因分析
静态配置的引用污染与闭包陷阱
moduleColumn.getColumns返回的是静态数组的引用,首次添加Action列后,该数组被修改并永久保留。后续渲染时,代码判断到最后一列是Action就不再创建新的列对象,导致首次渲染时创建的render函数被复用——而这个render函数通过闭包捕获了首次渲染时的props值,后续主组件props更新时,旧的render函数无法获取新的props,最终导致ActionOuvrage组件拿到的始终是旧值,无法交互。Ant Design Table的缓存机制
Ant Design Table会根据columns数组的引用和列对象的key进行渲染缓存。如果columns数组的引用未变化,且列对象未重新创建,Table会复用之前的列渲染结果,不会重新执行render函数,自然不会更新子组件的props。
解决方案
方案一:每次重新创建Action列(已验证有效)
就是你修改后的方式:每次先移除已存在的Action列,再重新添加新的列对象。这样每次生成的Action列都是全新的对象,render函数会捕获当前最新的props,Table也会触发该列的重新渲染。
方案二:避免修改静态配置,操作拷贝后的数组
不直接修改moduleColumn返回的静态数组,先做浅拷贝再操作,既不会污染静态配置,也能保证每次columns都是新的引用:
// 浅拷贝静态列数组,避免修改原静态配置 const columns = [...moduleColumn.getColumns(columnName)]; // 检查是否已有Action列,有则移除 if (columns[columns.length - 1]?.title === 'Action') { columns.pop(); } // 添加新的Action列 columns.push({ title: "Action", dataIndex: "id", key: "id", align: "center", render: (text, record, index) => ( <ActionOuvrage text={text} record={record} index={index} ouvrage={props.ouvrage} refData={props.refData} handleShownComponent={props.handleShownComponent} handleDataAction={props.handleDataAction} handleIdOuvrage={props.handleIdOuvrage} loadOuvrageData={loadOuvrageData} /> ), }); setTableData(prev => ({ ...prev, data: fetchedData, columns, total: count }));
方案三:给Action列设置动态key触发重渲染
如果不想每次移除再添加,可以给Action列的key设置动态值(比如结合props的依赖项),当props变化时,key变化会让Table认为这是新的列,从而重新渲染:
const columns = [...moduleColumn.getColumns(columnName)]; const actionColKey = `action-${props.ouvrage?.id || Date.now()}`; let actionColIndex = columns.findIndex(col => col.title === 'Action'); if (actionColIndex === -1) { // 没有则添加新的Action列 columns.push({ title: "Action", dataIndex: "id", key: actionColKey, align: "center", render: (text, record, index) => ( <ActionOuvrage text={text} record={record} index={index} ouvrage={props.ouvrage} refData={props.refData} handleShownComponent={props.handleShownComponent} handleDataAction={props.handleDataAction} handleIdOuvrage={props.handleIdOuvrage} loadOuvrageData={loadOuvrageData} /> ), }); } else { // 已有则更新列的key和render函数 columns[actionColIndex] = { ...columns[actionColIndex], key: actionColKey, render: (text, record, index) => ( <ActionOuvrage text={text} record={record} index={index} ouvrage={props.ouvrage} refData={props.refData} handleShownComponent={props.handleShownComponent} handleDataAction={props.handleDataAction} handleIdOuvrage={props.handleIdOuvrage} loadOuvrageData={loadOuvrageData} /> ), }; } setTableData(prev => ({ ...prev, data: fetchedData, columns, total: count }));
内容的提问来源于stack exchange,提问作者GisCat

