首页 > 解决方案 > 支持可变数据的 React 表库 ( useRef )

问题描述

基本上我正在寻找可以将可变对象(具体是 useRef 对象)作为要显示的主要数据源的反应表库。

基本上我想做这样的事情:

const TableViewComponent = () =>{
const tableData = useRef([{ rowName: 'test', value:0}] -> basically an array of objects that represents data for every row (the structure doesnt matter)
# code that receives data from server and updates the tableData.current with the data needed

return(
<Table data={tableData.current}/>
)
}

基本上,由于我从服务器收到一堆消息并且我不断更新数据(行数保持不变),我不想每次都重新渲染表格。所以我想使用 useRef 来更改表格上显示的数据,而不会触发反应的重新渲染。

我不确定它是否可以完成,但感谢任何帮助:)。我尝试了 react-table、rc-table 但它们似乎没有用。

标签: javascriptreactjsreact-hooks

解决方案


Basically, it looks to me like you'll have to do it yourself.

There's some libraries that might help you (like useTable which focuses on headless components) but I don't think they offer what you're looking for.

So let's quickly do a mock-up! (note: this is a quick sketch, assume that the undefined variables are stored somewhere else or are given from the fetch)

function useTableData({ initialData, itemsPerPage, ...etc }) {
    const data = useRef(initialData);
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        data.current = fetchFromSomeWhere(...etc);
        () => (data.current = null);
    }, [etc /* Place other dependencies that invalidate out data here*/]);

    const handleNewPage = useCallback(
        async ({ pageIndex }) => {
            if (!data.current[pageIndex * itemsPerPage]) {
                setLoading(true);
                data.current = [...data.current, await fetchMoreData(pageIndex)];
            }
            setLoading(false);
            return data.current;
        },
        [itemsPerPage, data, setLoading],
    );

    return [data, handleNewPage, loading];
}

Notice how every single thing returned from this hook is a constant reference except for the loading! Meaning, that we can safely pass this to a memoized table, and it won't trigger any re-renders.

const TableContainer = memo(etc => {
    const [data, handleNewPage, loading] = useDataForTable(...etc);
    return (
        <>
            {loading && <Spinner />}
            {/* Look ma, no expensive-renders! */}
            <Body {...{ data }} />
            <Pagination {...{ handleNewPage }} />
            <OtherActions />
        </>
    );
});

However, you might have noticed that the body won't re-render when we click on the next page! Which was the whole point of pagination! So now we need some sort of state that'll force the Body to re-render

const TableContainer = memo(etc => {
    const [currentPage, setPage] = useState(0);
    const [data, handleNewPage, loading] = useDataForTable(...etc);
    return (
        <>
            {loading && <Spinner />}
            {/* We're still re-rendering here :( */}
            <Body {...{ data, currentPage }} />
            <Footer {...{ handleNewPage, setPage }} />
            <OtherActions />
        </>
    );
});

And so we're left with a table that to use properly we need to:

  1. Exercise restraint and properly invalidate old data. Otherwise, we'd be displaying incorrect data.
  2. 'Unpack' the data from the current ref on the Body component, and then render it.

In essence, after all that work we're still left with a solution isn't particularly attractive, unless you have some really complicated actions or some expensive scaffolding around the TableComponent itself. This might however be your case.


推荐阅读