Tanstack React Table数据更新后Table对象未更新的问题排查
问题:TanStack React Table数据更新后子组件不重新渲染
使用TanStack React Table时,数据更新后Table对象未同步触发子组件<SageTableBody>重新渲染,核心代码如下:
import { useQuery } from "@tanstack/react-query"; import { createColumnHelper, flexRender, Table } from "@tanstack/react-table"; import { useReactTable, getCoreRowModel as defaultGetCoreRowModel, } from "@tanstack/react-table"; import { StrictMode, useMemo, memo } from "react"; import { createRoot } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); const columnHelper = createColumnHelper<User>(); interface User { id: number; name: string; age: number; email: string; status: "active" | "inactive"; createdAt: string; } const fetchData = async (): Promise<User[]> => { return new Promise((resolve) => { setTimeout(() => { const users: User[] = Array.from({ length: 10 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: Math.floor(Math.random() * 60) + 18, email: `user${i + 1}@example.com`, status: Math.random() > 0.5 ? "active" : "inactive", createdAt: new Date( Date.now() - Math.floor(Math.random() * 10000000000) ).toISOString(), })); resolve(users); }, 3000); }); }; const columns = [ columnHelper.accessor("id", { header: "ID", footer: (info) => info.column.id, size: 40, }), columnHelper.accessor("name", { header: "Name", footer: (info) => info.column.id, size: 100, }), columnHelper.accessor("age", { header: "Age", footer: (info) => info.column.id, size: 40, }), columnHelper.accessor("email", { header: "Email", footer: (info) => info.column.id, size: 200, }), columnHelper.accessor("status", { header: "Status", footer: (info) => info.column.id, size: 60, }), columnHelper.accessor("createdAt", { header: "Created At", cell: (info) => new Date(info.getValue()).toLocaleDateString(), footer: (info) => info.column.id, size: 100, }), ]; const App = () => { const { data, isLoading } = useQuery({ queryKey: ["fetch"], queryFn: async () => { return fetchData(); }, initialData: [], }); return <SageTable data={data} />; }; interface SageTableProps { data: User[]; } const SageTable = memo(({ data }: SageTableProps) => { const table = useReactTable({ data, columns, getCoreRowModel: defaultGetCoreRowModel(), columnResizeMode: "onChange", }); const columnSizeVars = useMemo(() => { const headers = table.getFlatHeaders(); const colSizes: { [key: string]: number } = {}; for (let i = 0; i < headers.length; i++) { const header = headers[i]!; colSizes[`--header-${header.id}-size`] = header.getSize(); colSizes[`--col-${header.column.id}-size`] = header.column.getSize(); } return colSizes; }, [table.getState().columnSizingInfo, table.getState().columnSizing]); return ( <div className="table" style={{ ...columnSizeVars, width: table.getTotalSize(), }} > <MemoizedTableBody table={table} /> </div> ); }); interface SageTableBodyProps { table: Table<User>; } const SageTableBody = ({ table }: SageTableBodyProps) => { return ( <div> {table.getRowModel().rows.map((row) => ( <div className="table__body__item" style={{ display: "flex" }} key={row.id} > {row.getVisibleCells().map((cell) => ( <div key={cell.id} style={{ border: "1px solid gray", width: `calc(var(--col-${cell.column.id}-size) * 1px)`, }} > {flexRender(cell.column.columnDef.cell, cell.getContext())} </div> ))} </div> ))} </div> ); }; const MemoizedTableBody = memo( SageTableBody, (prev, next) => prev.table.options.data === next.table.options.data ) as typeof SageTableBody; const rootElement = document.getElementById("root"); if (!rootElement) throw new Error("Failed to find the root element"); createRoot(rootElement).render( <StrictMode> <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </StrictMode> );
根据官方文档描述:
当data选项的引用发生变化(通过Object.is比较)时,表格会重新处理数据,依赖核心数据模型的各类处理(如分组、排序、筛选等)也会重新执行。
但实际中table.options.data的引用未触发预期变化,导致子组件不重新渲染,且Table对象没有提供setData方法。
解决方案
1. 移除自定义memo比较函数(最简方案)
问题根源在于自定义的memo比较函数逻辑不可靠,table.options.data不能准确反映数据更新状态。直接使用memo默认的浅比较即可:
const MemoizedTableBody = memo(SageTableBody) as typeof SageTableBody;
当data更新时,useReactTable会返回全新的table对象,默认浅比较会检测到props变化,触发子组件重新渲染。
2. 基于data引用的自定义比较(保留性能优化)
如果需要手动控制渲染时机,可以将data作为props传递给子组件,直接比较data的引用:
// 在SageTable中修改传递的props <MemoizedTableBody table={table} data={data} /> // 修改MemoizedTableBody的定义和比较函数 interface SageTableBodyProps { table: Table<User>; data: User[]; } const MemoizedTableBody = memo( SageTableBody, (prev, next) => prev.data === next.data ) as typeof SageTableBody;
3. 基于核心数据模型的比较
另一种可靠方式是比较表格的核心数据模型(如rowModel),因为数据更新时rowModel会重新生成:
const MemoizedTableBody = memo( SageTableBody, (prev, next) => { return prev.table.getRowModel().rows === next.table.getRowModel().rows; } ) as typeof SageTableBody;
内容的提问来源于stack exchange,提问作者SageJustus
相关产品推荐
相关产品推荐

