實(shí)戰(zhàn):基于 @refinedev/react-table 的 useTable 分頁(yè)實(shí)現(xiàn)與原理)
Refine TanStack Table 分頁(yè)實(shí)戰(zhàn)基于 refinedev/react-table 的 useTable 分頁(yè)實(shí)現(xiàn)與原理【免費(fèi)下載鏈接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/refine本指南以 Refine 官方文檔中useTable分頁(yè)章節(jié)的可運(yùn)行示例_partial-pagination-live-preview.md為骨架完整講解如何用refinedev/react-table在 Refine v5 項(xiàng)目中實(shí)現(xiàn)服務(wù)端/客戶(hù)端分頁(yè)表格從安裝、完整可復(fù)制的分頁(yè) UI 代碼到setPageIndex、setPageSize、getPrePaginationRowModel等 TanStack Table API 的逐一拆解并結(jié)合refinedev/react-table的源碼packages/react-table/src/useTable/index.ts與測(cè)試用例packages/react-table/src/useTable/index.spec.ts講清底層工作原理。讀完你可以獨(dú)立搭建一個(gè)帶頁(yè)碼跳轉(zhuǎn)、上一頁(yè)/下一頁(yè)、每頁(yè)條數(shù)切換的 headless 分頁(yè)表格。背景headless 的 TanStack Table 適配器Refine 官方為 TanStack Table 中明確說(shuō)明所有 TanStack Table 的特性都被支持官方示例可以直接復(fù)制粘貼到項(xiàng)目中使用。useTable是從refinedev/core包中的useTable擴(kuò)展而來(lái)底層通過(guò)useList獲取數(shù)據(jù)。也就是說(shuō)你既能使用 coreuseTable的全部能力資源解析、數(shù)據(jù)提供器對(duì)接、syncWithLocation、實(shí)時(shí)更新等也能使用 TanStack Table 的全部表格能力。安裝使用你習(xí)慣的包管理器安裝適配包npm install refinedev/react-table # 或 pnpm add refinedev/react-table # 或 yarn add refinedev/react-table該包內(nèi)部依賴(lài)tanstack/react-tableRefine 會(huì)將其作為依賴(lài)一并安裝無(wú)需手動(dòng)安裝 TanStack Table 本身。完整示例一個(gè)帶分頁(yè)控制的帖子列表下面這段代碼完整來(lái)自官方分頁(yè)示例見(jiàn) documentation/docs/packages/tanstack-table/examples/_partial-pagination-live-preview.md。它渲染一個(gè)posts資源列表并在表格下方構(gòu)建了一個(gè)完全自繪的分頁(yè)條首頁(yè)/上一頁(yè)/下一頁(yè)/末頁(yè)四個(gè)按鈕、當(dāng)前頁(yè)碼與總頁(yè)數(shù)、跳頁(yè)輸入框、每頁(yè)條數(shù)下拉選擇以及總行數(shù)統(tǒng)計(jì)。import React from react; import { useTable } from refinedev/react-table; import { ColumnDef, flexRender } from tanstack/react-table; interface IPost { id: number; title: string; content: string; status: published | draft | rejected; } const PostList: React.FC () { const columns React.useMemoColumnDefIPost[]( () [ { id: id, header: ID, accessorKey: id, }, { id: title, header: Title, accessorKey: title, }, { id: status, header: Status, accessorKey: status, }, { id: createdAt, header: CreatedAt, accessorKey: createdAt, }, ], [], ); const { reactTable: { getHeaderGroups, getRowModel, // 分頁(yè)相關(guān) API getState, setPageIndex, getCanPreviousPage, getPageCount, getCanNextPage, nextPage, previousPage, setPageSize, getPrePaginationRowModel, }, } useTable({ columns, }); return ( div table thead {getHeaderGroups().map((headerGroup) ( tr key{headerGroup.id} {headerGroup.headers.map((header) { return ( th key{header.id} {header.isPlaceholder ? null : ( {flexRender( header.column.columnDef.header, header.getContext(), )} / )} /th ); })} /tr ))} /thead tbody {getRowModel().rows.map((row) { return ( tr key{row.id} {row.getVisibleCells().map((cell) { return ( td key{cell.id} {flexRender( cell.column.columnDef.cell, cell.getContext(), )} /td ); })} /tr ); })} /tbody /table {/* 分頁(yè) UI 可以任意自繪這里只是最基礎(chǔ)的實(shí)現(xiàn) */} div button onClick{() setPageIndex(0)} disabled{!getCanPreviousPage()} {} /button button onClick{() previousPage()} disabled{!getCanPreviousPage()} {} /button button onClick{() nextPage()} disabled{!getCanNextPage()} {} /button button onClick{() setPageIndex(getPageCount() - 1)} disabled{!getCanNextPage()} {} /button span divPage/div strong {getState().pagination.pageIndex 1} of {getPageCount()} /strong /span span | Go to page: input typenumber defaultValue{getState().pagination.pageIndex 1} onChange{(e) { const page e.target.value ? Number(e.target.value) - 1 : 0; setPageIndex(page); }} / /span select value{getState().pagination.pageSize} onChange{(e) { setPageSize(Number(e.target.value)); }} {[10, 20, 30, 40, 50].map((pageSize) ( option key{pageSize} value{pageSize} Show {pageSize} /option ))} /select /div div{getPrePaginationRowModel().rows.length} Rows/div /div ); };把PostList掛到路由上并確保Refine中注冊(cè)了posts資源與對(duì)應(yīng)數(shù)據(jù)提供器即可運(yùn)行。分頁(yè) API 逐一拆解示例中從useTable()返回的reactTable對(duì)象里解構(gòu)出來(lái)的 9 個(gè)分頁(yè)相關(guān) API都是 TanStack Table v8 的標(biāo)準(zhǔn)接口API作用示例中的用法getState()讀取當(dāng)前表格狀態(tài)含pagination.pageIndex、pagination.pageSizegetState().pagination.pageIndex 1顯示當(dāng)前頁(yè)TanStack 的pageIndex從 0 開(kāi)始顯示時(shí) 1setPageIndex(index)跳轉(zhuǎn)到指定頁(yè)0 基索引首頁(yè)按鈕setPageIndex(0)末頁(yè)按鈕setPageIndex(getPageCount() - 1)跳頁(yè)輸入框把用戶(hù)輸入的 1 基頁(yè)碼減 1 后傳入getCanPreviousPage()是否存在上一頁(yè)即是否在第一頁(yè)作為、按鈕的disabled條件getPageCount()總頁(yè)數(shù)末頁(yè)按鈕與Page x of y展示getCanNextPage()是否存在下一頁(yè)即是否在末頁(yè)作為、按鈕的disabled條件nextPage()下一頁(yè)按鈕點(diǎn)擊事件previousPage()上一頁(yè)按鈕點(diǎn)擊事件setPageSize(size)設(shè)置每頁(yè)條數(shù)select的onChange取值來(lái)自[10, 20, 30, 40, 50]getPrePaginationRowModel()返回分頁(yè)前的行模型全量行g(shù)etPrePaginationRowModel().rows.length展示總行數(shù)兩點(diǎn)關(guān)鍵提示pageIndex是 0 基、currentPage是 1 基TanStack Table 內(nèi)部pageIndex從 0 開(kāi)始計(jì)數(shù)因此顯示頁(yè)碼要1跳頁(yè)輸入框要把用戶(hù)輸入減 1。跳頁(yè)輸入的邊界處理const page e.target.value ? Number(e.target.value) - 1 : 0;—— 輸入為空時(shí)回退到第 0 頁(yè)第一頁(yè)避免NaN傳入。底層原理useTable 是如何把分頁(yè)與 Refine 數(shù)據(jù)請(qǐng)求聯(lián)動(dòng)起來(lái)的官方分頁(yè)章節(jié)文檔指出「setPageSize等方法的每次變更都會(huì)觸發(fā)一次新的數(shù)據(jù)提供器請(qǐng)求」。要理解這一點(diǎn)需要看 packages/react-table/src/useTable/index.ts 的實(shí)現(xiàn)const isPaginationEnabled refineCoreProps.pagination?.mode ! off; // ... const reactTableResult useReactTableTData({ data: data?.data ?? [], // ... initialState: { pagination: { pageIndex: currentPage - 1, pageSize: pageSizeCore, }, // ... }, pageCount, manualPagination: true, // ... });關(guān)鍵機(jī)制有三條manualPagination: true手動(dòng)分頁(yè)TanStack Table 不再在客戶(hù)端自動(dòng)切片data而是把分頁(yè)狀態(tài)完全交給開(kāi)發(fā)者/外層框架控制配合 Refine 的服務(wù)端數(shù)據(jù)獲取。初始狀態(tài)雙向映射coreuseTable的currentPage1 基映射為 TanStack 的pageIndex0 基即currentPage - 1pageSize直接透?jìng)鳌M瑫r(shí)把 core 返回的pageCount作為 TanStack 的總頁(yè)數(shù)來(lái)源。狀態(tài)同步useEffect 反向聯(lián)動(dòng)當(dāng)用戶(hù)在 UI 上調(diào)用setPageIndex/setPageSize改變 TanStack 狀態(tài)后兩個(gè)useEffect會(huì)把新的pageIndex 1與pageSize寫(xiě)回 core 的setCurrentPage/setPageSizeCore從而觸發(fā)useList重新請(qǐng)求數(shù)據(jù)useEffect(() { if (pageIndex ! undefined) { setCurrentPage(pageIndex 1); } }, [pageIndex]); useEffect(() { if (pageSize ! undefined) { setPageSizeCore(pageSize); } }, [pageSize]);測(cè)試用例印證了這一行為packages/react-table/src/useTable/index.spec.ts默認(rèn)無(wú)參數(shù)時(shí)state.pagination?.pageIndex為0、pageSize為10、pageCount為1傳入pagination: { currentPage: 2, pageSize: 1 }后core 側(cè)currentPage為2、pageSize為1TanStack 側(cè)pageIndex為1、pageSize為1pageCount變?yōu)?。配置項(xiàng)pagination.currentPage / pageSize / mode分頁(yè)行為通過(guò)useTable的refineCoreProps.pagination配置完整參數(shù)表見(jiàn) useTable 官方文檔useTable({ refineCoreProps: { pagination: { currentPage: 2, // 初始頁(yè)碼1 基默認(rèn) 1 pageSize: 10, // 初始每頁(yè)條數(shù)默認(rèn) 10 mode: server, // server | client | off默認(rèn) server }, }, });參數(shù)類(lèi)型默認(rèn)值說(shuō)明pagination.currentPagenumber1初始頁(yè)碼1 基pagination.pageSizenumber10初始每頁(yè)條數(shù)pagination.modeoff \| server \| clientserver分頁(yè)模式見(jiàn)下三種mode的含義server默認(rèn)服務(wù)端分頁(yè)。數(shù)據(jù)提供器根據(jù)currentPage與pageSize請(qǐng)求對(duì)應(yīng)頁(yè)的數(shù)據(jù)TanStack Table 通過(guò)manualPagination: true不做客戶(hù)端切片。這也是示例中g(shù)etPrePaginationRowModel().rows.length能拿到總數(shù)的原因——總行數(shù)來(lái)自服務(wù)端返回的total。client客戶(hù)端分頁(yè)。一次性拉取全量記錄由 TanStack Table 在客戶(hù)端完成切片。適用于數(shù)據(jù)量較小的場(chǎng)景。off關(guān)閉分頁(yè)一次請(qǐng)求拉取全部記錄。從源碼看mode: off時(shí)isPaginationEnabled為false同時(shí)排序sorting.length 0或篩選crudFilters.length 0變化后只有分頁(yè)啟用時(shí)才會(huì)把頁(yè)碼重置回第 1 頁(yè)if (sorting.length 0 isPaginationEnabled !isFirstRender) { setCurrentPage(1); }與排序、篩選的聯(lián)動(dòng)換頁(yè)重置除了分頁(yè)本身源碼中還有兩處值得注意的聯(lián)動(dòng)邏輯packages/react-table/src/useTable/index.ts 第 135-176 行排序變化sorting變更時(shí)將 TanStack 的排序狀態(tài)轉(zhuǎn)換回 Refine 的CrudSortingdesc?desc其余為asc并通過(guò)setSorters寫(xiě)回 core觸發(fā)重新請(qǐng)求列篩選變化columnFilters變更時(shí)通過(guò)工具函數(shù)columnFiltersToCrudFilters/crudFiltersToColumnFilters與 core 的 filters 狀態(tài)雙向同步并補(bǔ)回被移除的篩選器getRemovedFilters。二者的共同點(diǎn)是在分頁(yè)啟用且非首次渲染時(shí)把頁(yè)碼重置回第 1 頁(yè)避免用戶(hù)停留在第 5 頁(yè)時(shí)排序/篩選導(dǎo)致越界。測(cè)試用例同樣覆蓋了這些行為index.spec.ts中針對(duì)sorters.mode、filters.mode的it.each用例驗(yàn)證manualSorting/manualFiltering與服務(wù)端模式的對(duì)應(yīng)關(guān)系。URL 狀態(tài)同步syncWithLocation如果希望分頁(yè)狀態(tài)以及其他表格狀態(tài)同步到 URL 查詢(xún)參數(shù)從而支持收藏、分享鏈接可以啟用syncWithLocationuseTable({ refineCoreProps: { syncWithLocation: true, }, });啟用后pageIndex、pageSize等狀態(tài)會(huì)自動(dòng)編碼進(jìn) URL queryURL 變化時(shí)表格狀態(tài)也會(huì)隨之恢復(fù)。默認(rèn)它會(huì)讀取Refine組件上的syncWithLocation全局配置。進(jìn)階客戶(hù)端分頁(yè)與關(guān)閉分頁(yè)按數(shù)據(jù)規(guī)模選擇模式// 客戶(hù)端分頁(yè)全量拉取、本地切片 useTable({ refineCoreProps: { pagination: { mode: client, }, }, }); // 關(guān)閉分頁(yè)一次拉取全部記錄 useTable({ refineCoreProps: { pagination: { mode: off, }, }, });注意客戶(hù)端分頁(yè)模式下TanStack Table 的getFilteredRowModel、getSortedRowModel等客戶(hù)端行模型仍然可用源碼中僅在對(duì)應(yīng)服務(wù)端模式開(kāi)啟時(shí)才置為undefined因此客戶(hù)端分頁(yè)可以方便地與客戶(hù)端排序/篩選組合使用。參考示例倉(cāng)庫(kù)內(nèi)完整的可運(yùn)行示例見(jiàn) examples/table-react-table-basic它與本指南的分頁(yè)示例同源對(duì)應(yīng)文檔頁(yè)面的 CodeSandbox 鏈接。相關(guān)文檔還包括useTable Hook 完整參考屬性 / 返回值 / FAQTanStack Table 集成介紹Tables 指南guides-conceptscore useTable 數(shù)據(jù) Hook 文檔適配器源碼 與 測(cè)試用例【免費(fèi)下載鏈接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/refine創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考