如果您正在使用 React,一个非常常见的陷阱会导致无限渲染。如果您未能为您的 列、数据 或 状态 提供稳定的引用,React 将会进入一个由表格状态的任何更改触发的无限重渲染循环。
为什么会发生这种情况?这是 TanStack Table 的 bug 吗?不,不是。这是 React 的基本工作原理,正确管理您的列、数据和状态将可以防止这种情况发生。
TanStack Table 的设计是在传递给表格的 数据 或 列 发生更改时,或在表格的任何状态发生更改时,触发一次重新渲染。
未能为 列 或 数据 提供稳定的引用可能会导致无限的重渲染循环。
export default function MyComponent() {
//😵 BAD: This will cause an infinite loop of re-renders because `columns` is redefined as a new array on every render!
const columns = [
// ...
];
//😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render!
const data = [
// ...
];
//❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop!
const table = useReactTable({
columns,
data,
});
return <table>...</table>;
}
export default function MyComponent() {
//😵 BAD: This will cause an infinite loop of re-renders because `columns` is redefined as a new array on every render!
const columns = [
// ...
];
//😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render!
const data = [
// ...
];
//❌ Columns and data are defined in the same scope as `useReactTable` without a stable reference, will cause infinite loop!
const table = useReactTable({
columns,
data,
});
return <table>...</table>;
}
在 React 中,您可以通过在组件外部/上方定义变量,或者使用 useMemo 或 useState,或者使用第三方状态管理库(如 Redux 或 React Query 😉)来为变量提供“稳定”的引用。
//✅ OK: Define columns outside of the component
const columns = [
// ...
];
//✅ OK: Define data outside of the component
const data = [
// ...
];
// Usually it's more practical to define columns and data inside the component, so use `useMemo` or `useState` to give them stable references
export default function MyComponent() {
//✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD: This will not cause an infinite loop of re-renders because `data` is a stable reference
const [data, setData] = useState(() => [
// ...
]);
// Columns and data are defined in a stable reference, will not cause infinite loop!
const table = useReactTable({
columns,
data,
});
return <table>...</table>;
}
//✅ OK: Define columns outside of the component
const columns = [
// ...
];
//✅ OK: Define data outside of the component
const data = [
// ...
];
// Usually it's more practical to define columns and data inside the component, so use `useMemo` or `useState` to give them stable references
export default function MyComponent() {
//✅ GOOD: This will not cause an infinite loop of re-renders because `columns` is a stable reference
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD: This will not cause an infinite loop of re-renders because `data` is a stable reference
const [data, setData] = useState(() => [
// ...
]);
// Columns and data are defined in a stable reference, will not cause infinite loop!
const table = useReactTable({
columns,
data,
});
return <table>...</table>;
}
即使您为初始的 列 和 数据 提供了稳定的引用,如果您原地修改它们,仍然可能遇到无限循环。这是一个常见的陷阱,您可能一开始没有注意到。像内联的 data.filter() 这样的操作,如果操作不当,也可能导致无限循环。
export default function MyComponent() {
//✅ GOOD
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD (React Query provides stable references to data automatically)
const { data, isLoading } = useQuery({
//...
});
const table = useReactTable({
columns,
//❌ BAD: This will cause an infinite loop of re-renders because `data` is mutated in place (destroys stable reference)
data: data?.filter(d => d.isActive) ?? [],
});
return <table>...</table>;
}
export default function MyComponent() {
//✅ GOOD
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD (React Query provides stable references to data automatically)
const { data, isLoading } = useQuery({
//...
});
const table = useReactTable({
columns,
//❌ BAD: This will cause an infinite loop of re-renders because `data` is mutated in place (destroys stable reference)
data: data?.filter(d => d.isActive) ?? [],
});
return <table>...</table>;
}
为了防止无限循环,您应该始终对数据转换进行记忆化处理。这可以通过 useMemo 或类似的方法来实现。
export default function MyComponent() {
//✅ GOOD
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD
const { data, isLoading } = useQuery({
//...
});
//✅ GOOD: This will not cause an infinite loop of re-renders because `filteredData` is memoized
const filteredData = useMemo(() => data?.filter(d => d.isActive) ?? [], [data]);
const table = useReactTable({
columns,
data: filteredData, // stable reference!
});
return <table>...</table>;
}
export default function MyComponent() {
//✅ GOOD
const columns = useMemo(() => [
// ...
], []);
//✅ GOOD
const { data, isLoading } = useQuery({
//...
});
//✅ GOOD: This will not cause an infinite loop of re-renders because `filteredData` is memoized
const filteredData = useMemo(() => data?.filter(d => d.isActive) ?? [], [data]);
const table = useReactTable({
columns,
data: filteredData, // stable reference!
});
return <table>...</table>;
}
当 React Forget 发布后,这些问题可能会成为过去。或者,直接使用 Solid.js... 🤓
大多数插件使用通常会在数据源更改时重置的状态,但有时您需要阻止这种情况发生,如果您正在外部过滤数据,或在查看数据时以不可变的方式编辑数据,或者只是对数据进行了任何不希望触发表格状态自动重置的外部操作。
在这些情况下,每个插件都提供了一种方法来禁用状态在数据或其他状态依赖项更改时自动重置。将其中任何一个设置为 false,就可以阻止自动重置的触发。
这是一个基于 React 的示例,展示了如何阻止表格的几乎所有状态像平常一样发生变化,同时我们编辑表格的 数据 源。
const [data, setData] = React.useState([])
const skipPageResetRef = React.useRef()
const updateData = newData => {
// When data gets updated with this function, set a flag
// to disable all of the auto resetting
skipPageResetRef.current = true
setData(newData)
}
React.useEffect(() => {
// After the table has updated, always remove the flag
skipPageResetRef.current = false
})
useReactTable({
...
autoResetPageIndex: !skipPageResetRef.current,
autoResetExpanded: !skipPageResetRef.current,
})
const [data, setData] = React.useState([])
const skipPageResetRef = React.useRef()
const updateData = newData => {
// When data gets updated with this function, set a flag
// to disable all of the auto resetting
skipPageResetRef.current = true
setData(newData)
}
React.useEffect(() => {
// After the table has updated, always remove the flag
skipPageResetRef.current = false
})
useReactTable({
...
autoResetPageIndex: !skipPageResetRef.current,
autoResetExpanded: !skipPageResetRef.current,
})
现在,当我们更新数据时,上述表格状态将不会自动重置!
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。