React函数组件渲染旧数据:拆分组件后数据不显示
组件拆分后数据无法正常显示问题
我将代码拆分为独立组件后,SimpleTable组件中的两个输入框显示空白,但未组件化的表格能正常展示数据。怀疑这和闭包有关,但不确定具体原因及解决方法。
代码示例
App.tsx
import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { SimpleTable } from './components/SimpleTable/SimpleTable'; import ITableCol from './components/SimpleTable/ITableCol'; interface IItem { userId: number; id: number; title: string; body: string; } interface IIdItem { [key: string]: number; } export const App = () => { const getItems = async () => { const res = await axios.get('https://jsonplaceholder.typicode.com/posts'); const twoItems = [res.data[0], res.data[1]]; setItems(twoItems); }; const getTableCols = (): ITableCol[] => { return [ { title: 'title', renderFn: renderTitle, }, { title: 'input', renderFn: renderInput, }, ]; }; const processData = () => { let _idValues = { ...idValues }; for (const item of items) { if (!_idValues[item.title]) { _idValues[item.title] = item.id * 5; } } return _idValues; }; const renderTitle = (item: IItem) => { return <div className="">{item.title}</div>; }; const handleChange = (e: React.ChangeEvent<any>) => {}; const renderInput = (item: IItem) => { const valueItem = idValues[item.title]; return ( <div className=""> <div>Id: {item.id}</div> <div> <input type="number" value={valueItem} onChange={handleChange} /> </div> </div> ); }; useEffect(() => { getItems(); }, []); const [items, setItems] = useState<IItem[]>([]); const [idValues, setIdValues] = useState<IIdItem>({}); const [tableCols, setTableCols] = useState<ITableCol[]>([]); useEffect(() => { setTableCols(getTableCols()); setIdValues(processData()); }, [items]); return ( <div className="p-2"> <h1>State Issue Example</h1> <div className="mb-5"> <div>Simple Table</div> <SimpleTable data={items} cols={tableCols} /> </div> <div> <div>Non Componentized Table</div> <div className="d-flex"> <div className="w-50"> <b>title</b> </div> <div className="w-50"> <b>input</b> </div> </div> </div> <div> {items.map((item) => { return ( <div className="d-flex"> <div className="w-50">{renderTitle(item)}</div> <div className="w-50">{renderInput(item)}</div> </div> ); })} </div> </div> ); };
SimpleTable.tsx
import React from 'react'; import ITableCol from '../SimpleTable/ITableCol'; type Props = { cols: ITableCol[]; data: any[]; }; export const SimpleTable: React.FC<Props> = (props) => { return ( <div> <div className="d-flex"> {props.cols.map((col) => { return ( <div className="w-50"> <b>{col.title}</b> </div> ); })} </div> {props.data.map((data, index) => { return ( <div className="d-flex" key={'data-' + index}> {props.cols.map((col, index) => { return ( <div key={data.id} className="w-50"> {col.renderFn(data, index)} </div> ); })} </div> ); })} <div>Data Size: {props.data.length}</div> </div> ); };
ITableCol.tsx
export default interface ITableCol { renderFn: (item: any, index: number) => void; title: string; }
问题原因
核心问题是闭包捕获了旧的state值:
- 当你在依赖
items的useEffect中调用setTableCols(getTableCols())时,getTableCols返回的renderFn(即renderInput)会捕获当时的idValues(初始为空对象)。 - 后续
processData执行并更新idValues时,tableCols中存储的renderFn引用的仍是旧的idValues,所以SimpleTable组件里的输入框拿不到最新数据。 - 未组件化的表格是在App的render阶段直接调用
renderInput,每次render都会获取最新的idValues,因此能正常显示。
解决方案
方案一:移除tableCols状态(推荐)
不需要将tableCols存入state,直接在render时生成,确保每次渲染都能拿到最新的renderFn(捕获最新的idValues):
- 删除App.tsx中的
tableCols状态定义:
// 删掉这行 const [tableCols, setTableCols] = useState<ITableCol[]>([]);
- 删除依赖
items的useEffect中的setTableCols调用:
useEffect(() => { // 删掉这行 // setTableCols(getTableCols()); setIdValues(processData()); }, [items]);
- 修改SimpleTable的调用,直接传入
getTableCols()的结果:
<SimpleTable data={items} cols={getTableCols()} />
方案二:传递最新的idValues给renderFn
如果必须保留tableCols状态,可以将idValues作为参数传递给renderFn,确保它能获取最新值:
- 修改ITableCol接口,添加
idValues参数:
export default interface ITableCol { renderFn: (item: any, index: number, idValues: IIdItem) => React.ReactNode; title: string; }
- 修改App.tsx中的
getTableCols,让它接收idValues并传递给renderInput:
const getTableCols = (idValues: IIdItem): ITableCol[] => { return [ { title: 'title', renderFn: renderTitle, }, { title: 'input', renderFn: (item, index) => renderInput(item, idValues), }, ]; };
- 修改
renderInput,接收传入的idValues:
const renderInput = (item: IItem, idValues: IIdItem) => { const valueItem = idValues[item.title]; return ( <div className=""> <div>Id: {item.id}</div> <div> <input type="number" value={valueItem} onChange={handleChange} /> </div> </div> ); };
- 更新依赖
items的useEffect,传入最新的idValues:
useEffect(() => { const newIdValues = processData(); setIdValues(newIdValues); setTableCols(getTableCols(newIdValues)); }, [items]);
- 修改SimpleTable.tsx,调用
renderFn时传入idValues(需要给SimpleTable添加idValuesprops):
type Props = { cols: ITableCol[]; data: any[]; idValues: IIdItem; // 添加这个 }; // ... {col.renderFn(data, index, props.idValues)}
同时修改App中SimpleTable的调用:
<SimpleTable data={items} cols={tableCols} idValues={idValues} />
内容的提问来源于stack exchange,提问作者smb
相关产品推荐
相关产品推荐

