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

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):

  1. 删除App.tsx中的tableCols状态定义:
// 删掉这行
const [tableCols, setTableCols] = useState<ITableCol[]>([]);
  1. 删除依赖items的useEffect中的setTableCols调用:
useEffect(() => {
  // 删掉这行
  // setTableCols(getTableCols());
  setIdValues(processData());
}, [items]);
  1. 修改SimpleTable的调用,直接传入getTableCols()的结果:
<SimpleTable data={items} cols={getTableCols()} />

方案二:传递最新的idValues给renderFn

如果必须保留tableCols状态,可以将idValues作为参数传递给renderFn,确保它能获取最新值:

  1. 修改ITableCol接口,添加idValues参数:
export default interface ITableCol {
  renderFn: (item: any, index: number, idValues: IIdItem) => React.ReactNode;
  title: string;
}
  1. 修改App.tsx中的getTableCols,让它接收idValues并传递给renderInput:
const getTableCols = (idValues: IIdItem): ITableCol[] => {
  return [
    {
      title: 'title',
      renderFn: renderTitle,
    },
    {
      title: 'input',
      renderFn: (item, index) => renderInput(item, idValues),
    },
  ];
};
  1. 修改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>
  );
};
  1. 更新依赖items的useEffect,传入最新的idValues:
useEffect(() => {
  const newIdValues = processData();
  setIdValues(newIdValues);
  setTableCols(getTableCols(newIdValues));
}, [items]);
  1. 修改SimpleTable.tsx,调用renderFn时传入idValues(需要给SimpleTable添加idValues props):
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 22:30:51