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

使用MUI Table结合TableVirtuoso实现排序时需手动滚动才生效的问题求助

MUI Table结合TableVirtuoso实现排序时需手动滚动才生效的问题求助

最近我在尝试用MUI Table组件搭配TableVirtuoso实现一个虚拟化表格,同时想给它加上排序功能,参考了官方文档里的排序示例。不过那个示例里的排序逻辑是在渲染行的时候处理的,但TableVirtuoso的用法是直接传入完整的数据源数组,然后通过itemContent参数指定每一行的渲染函数。

我的思路是:当数据源、排序方向或者排序列发生变化时,用useState和useEffect钩子先把数据源数组排好序,再传给TableVirtuoso组件。现在的问题是,虽然数组确实已经被正确排序了,但UI上完全看不到变化,必须手动滚动表格才会更新;就算排序前表格已经滚动到下方,我调用了scrollToIndex(0)也没法让UI同步更新。

我感觉修改数据源数组之后,并没有触发TableVirtuoso内部的重渲染,和预期的不一样。想请教一下各位,有没有办法在排序操作时强制触发表格的重渲染?或者有没有其他更合适的方案来实现这个虚拟化表格的排序功能?

组件代码

'use strict';

import React, {
forwardRef,
useState,
useEffect,
useRef,
} from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import TableSortLabel from '@mui/material/TableSortLabel';
import Paper from '@mui/material/Paper';
import { TableVirtuoso } from 'react-virtuoso';
import { useTranslation } from './contexts/TranslationContext';

export const VirtualTable = (props) => {
// get props
const {
columnConfig,
data: propData
} = props;

// get translation hook
const { t, locale } = useTranslation();

// get virtuoso ref
const virtuoso = useRef(null);

// get data state
const [data, setData] = useState(propData);

// get order state
const [order, setOrder] = useState('asc');

// get order by state
const [orderBy, setOrderBy] = useState(columnConfig[0].dataKey);

// handle change of propData, order and orderBy and sort data
useEffect(() => {
setData((oldData) => oldData.sort(getComparator(order, orderBy)));
}, [propData, order, orderBy]);

// prepare sort comparators
const descendingComparator = (a, b, orderBy) => {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
};

const getComparator = (order, orderBy) => {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
};

// prepare Virtuoso components
const virtuosoComponents = {
Scroller: forwardRef((props, ref) => (
<TableContainer component={Paper} {...props} ref={ref} />
)),
Table: (props) => <Table {...props} sx={{ borderCollapse: 'separate' }} />,
TableHead: forwardRef((props, ref) => <TableHead {...props} sx={{ boxShadow: 3 }} ref={ref} />),
TableRow: ({ item: _, ...props }) => <TableRow {...props} hover={true} />,
TableBody: forwardRef((props, ref) => <TableBody {...props} ref={ref} />),
};

// event handler to handle onClick for sorting table
const handleSort = (columnDataKey) => {
const isAsc = orderBy === columnDataKey && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(columnDataKey);
virtuoso.current.scrollToIndex(0);
};

// format cell data according to type
const getCellData = (row, column) => {
// switch type
switch(column.type) {
case 'string':
return row[column.dataKey];
case 'date':
if(row[column.dataKey] === '') {
return '';
}
return (new Date(row[column.dataKey])).toLocaleDateString(locale, {day: '2-digit', month: '2-digit', year: 'numeric'});
case 'actions':
return '';
default:
return t('Column type "{{type}}" is not valid', {type: column.type});
}
};

// prepare fixed header
const getFixedHeader = () => {
return (
<TableRow>
{
columnConfig.map((column) => (
<TableCell
key={column.dataKey}
align={column.align}
sx={{
width: typeof column.width !== 'undefined' ? column.width : undefined,
backgroundColor: 'background.paper',
}}
>
{typeof column.sortable !== 'undefined' && column.sortable === false
? column.label
: <TableSortLabel
active={orderBy === column.dataKey}
direction={orderBy === column.dataKey ? order : 'asc'}
onClick={() => handleSort(column.dataKey)}
>
{column.label}
</TableSortLabel>
}
</TableCell>
))
}
</TableRow>
);
};

// prepare row content
const getRowContent = (_index, row) => {
return (
<>
{columnConfig.map((column) => (
<TableCell
key={column.dataKey}
align={column.align}
>
{getCellData(row, column)}
</TableCell>
))}
</>
);
};

// render jsx
return (
<Paper style={{ width: '100%', height: 'calc(100vh - 160px' }} >
<TableVirtuoso
ref={virtuoso}
data={data}
fixedHeaderContent={getFixedHeader}
itemContent={getRowContent}
components={virtuosoComponents}
overscan={{ main: 5, reverse: 5 }}
/>
</Paper>
);
};

传入的columnConfig属性

const columnConfig = [
{
label: t('Begin'),
dataKey: 'begin',
align: 'left',
width: '10%',
type: 'date',
},
{
label: t('End'),
dataKey: 'end',
align: 'left',
width: '10%',
type: 'date',
sortable: false,
},
{
label: t('Event'),
dataKey: 'name',
align: 'left',
type: 'string',
},
{
label: t('Location'),
dataKey: 'location',
align: 'left',
width: '15%',
type: 'string',
},
{
label: t('Actions'),
dataKey: 'actions',
align: 'right',
width: '15%',
type: 'actions',
sortable: false,
actions: [
],
},
];

传入的data属性

const data = Array.from({ length: 100 }, (_, index) => {
const now = new Date();
const begin = (new Date()).setDate(now.getDate() + index);
return {
id: index,
begin: begin,
end: '',
name: `Event ${index}`,
location: `Location ${index}`,
actions: '',
};
});

备注:内容来源于stack exchange,提问作者bohrsty

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 11:22:34