@ehfuse/mui-virtual-data-table

中文文档
1.11.4发布于 2026/11/03MITTypeScriptES Module近 30 天下载 5770

与 Materi-UI 集成反应的高性能虚拟数据表组件

中文使用文档
文档目录15 节
  1. 特征
  2. 주요
  3. 安装
  4. 同伴依赖关系
  5. 页:1
  6. 快速启动
  7. 基本支持签名
  8. 列选项 (1.2.0)
  9. 页面滚动 (1.7. 0)
  10. 列大小调整(1.8.0)
  11. 行重排( 1. 9. 0)
  12. 移动列表 (1.6.0)
  13. 许可证
  14. 捐款
  15. 支助

MUI 虚拟数据表

一个高性能的虚拟数据表组件,用于与Material-UI集成进行React,以虚拟化渲染,无限卷轴,键盘导航,自定义型号为主.

(原始内容存档于2018-09-29). React QQ Material-UI QQ. 가상화,무한也.

特征

  • 🚀 高性能: 虚拟渲染用反应-虚拟来平滑地用大数据集滚动
  • ♾️ 无限滚动: 内置有自动加载指示器的无限滚动
  • ⌨️ 键盘导航: 完整的键盘支持(箭头键,PageUp/Down,Home/End)
  • 🎨 物质-UI 整合: 与MUI组件无缝集成
  • 🦓 斑马脱衣: 带有自定义颜色支持的可选交替行颜色
  • 🔧 类型脚本支持: 包含全面类型定义的完整类型脚本支持
  • 🔄 排序: 内置列排序功能
  • 🎯 可自定义: 高度自定义的列、样式和滚动条选项
  • 📊 空状态: 内建空状态处理自定义消息
  • 🔍 装入状态: 有淡出动画的平滑加载过渡
  • 📏 分组信头: 支持带有动态高度计算的分组列标题
  • 🎨 自定义滚动栏: 带有完全定制选项的美丽的自定义滚动条

주요

  • 🚀 고성능{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}你觉得呢
  • ♾️ 무한자동:인디케이터가也
  • ⌨️ 키보드:
  • 🎨 物质UI翻译:
  • 🦓 얼룩말{\fn黑体\fs20\shad2\2aH82\3aH20\4aH33\fscx95\3cH592001\be1}你觉得怎样
  • 🔧 类型脚本QQ:====================================================================================================== (======================================================================
  • 🔄 정렬내장된:
  • 🎯 커스터마이징컬럼:컬럼也
  • 📊 빈커스텀:지원하는也
  • 🔍 로딩{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你觉得呢
  • 📏 그룹동적:계산을也
  • 🎨 커스텀완전한:옵션을也

安装

npm install @ehfuse/mui-virtual-data-table

同伴依赖关系

此软件包需要以下对等依赖性:

npm install react react-dom @mui/material react-virtuoso

页:1

详细的API参考和使用示例:

快速启动

import { VirtualDataTable } from "@ehfuse/mui-virtual-data-table";
import type { DataColumn } from "@ehfuse/mui-virtual-data-table";

interface User {
    id: number;
    name: string;
    email: string;
}

const columns: DataColumn<User>[] = [
    { id: "id", text: "ID", width: 80 },
    { id: "name", text: "Name", width: 200, sortable: true },
    { id: "email", text: "Email", width: 250 },
];

const data: User[] = [
    { id: 1, name: "John Doe", email: "[email protected]" },
    { id: 2, name: "Jane Smith", email: "[email protected]" },
    // ... more data
];

function App() {
    return (
        <VirtualDataTable
            data={data}
            columns={columns}
            totalCount={data.length}
            loading={false}
        />
    );
}

基本支持签名

<VirtualDataTable
    // Required
    data={T[]}                              // Data array
    columns={DataColumn<T>[]}               // Column definitions
    totalCount={number}                     // Total data count

    // Optional - Styling
    striped={boolean | string}              // Zebra striping (true: default gray (#f5f5f5), string: custom color, default: false)
    rowDivider={boolean}                    // Show row borders (default: true)
    rowHeight={number}                      // Fixed row height in px (default: 50) — rows keep this height even when showFooter fills a taller container
    columnHeight={number}                   // Header height in px (default: 56, auto 2x for grouped headers)
    showPaper={boolean}                     // Wrap in Paper component (default: true)
    paddingX={string | number}              // Horizontal padding (default: "1rem")
    paddingTop={string | number}            // Top padding (default: 0)
    paddingBottom={string | number}         // Bottom padding (default: 0)
    variant={"default" | "notion"}          // Table look. "notion": Notion-like (40px header, 13px grey header text, cell dividers, flat hover) — matches @ehfuse/taskbox tables
    rowHoverColor={string}                  // Row hover background color (default: "#000000", auto-inverted brightness in dark mode)
    rowHoverOpacity={number}                // Row hover opacity 0-1 (default: 0.06)
    viewportBuffer={number | { top, bottom }} // Viewport outside pre-render buffer in px (default: rowHeight-based auto buffer)

    // Optional - Infinite Scroll
    loading={boolean}                       // Loading state (default: false)
    onLoadMore={(offset, limit) => void}    // Load more callback (enables infinite scroll when provided)
    // ⚠️ onLoadMore 계약: 호출되면 반드시 이행해서 data 가 늘어나거나 loading 이 토글돼야 합니다.
    //    (정렬/검색/필터로 목록을 1페이지로 되돌리면 컴포넌트가 자동으로 더보기 잠금을 풀어 재개합니다.)

    // Optional - Sorting
    sortBy={string}                         // Current sort field
    sortDirection={"asc" | "desc"}          // Current sort direction
    onSort={(id, direction) => void}        // Sort callback

    // Optional - Interactions
    onRowClick={(item, index) => void}      // Row click handler

    // Optional - Customization
    emptyMessage={string | React.ReactNode} // Empty state message (default: "NO DATA")
    scrollbars={VDTOverlayScrollbarProps}   // Custom scrollbar options
    LoadingComponent={React.ComponentType}  // Custom loading component

    // Optional - Layout (1.2.0)
    framed={boolean}                        // notion: outer border + paper padding (default: true). false = fill an already-bordered box
    headerTopBorder={boolean}               // notion + framed=false: draw a top line on the header row (table shares a box with a toolbar above)
    tableLayout={"fixed" | "auto"}          // "auto": column widths follow content, wide tables scroll horizontally
    rowHeightMode={"fixed" | "auto"}        // "auto": rowHeight becomes a minimum, rows grow with content
    autoHeight={boolean}                    // No virtualization / no own scroll — the table is as tall as its rows (page scrolls)
    renderFullWidthRow={(item, index) => ReactNode | null} // Group band row: one cell spanning all columns (null = normal row)
    fullWidthRowSx={SxProps}                // Style of the band row cell (background, padding)
    onRowContextMenu={(item, index, event) => void}

    // Optional - 1.3.0
    pinnedRows={T[]}                        // Rows pinned under the header (always visible, same columns/cells/band rows)
    pinnedRowsMaxHeight={number}            // Max height of the pinned area in px — it scrolls on its own beyond that
    pinnedRowsSx={SxProps}                  // Pinned area style (default: 1px strong bottom border)
    resetScrollOnDataChange={boolean}       // false: keep the scroll position when rows are removed (collapse groups); remount with key to reset
    rowKeyFromId={boolean}                  // Use getRowId as the React key of rows (rows with inline editors)
    onEndReached={() => void}               // Fired every time the end is reached (no minimum / no same-length latch)
    colorMode={"light" | "dark"}            // notion colors independent of the MUI palette mode
    borderColor={string}                    // notion: color for every line (outer frame, header line, cell dividers). Default NOTION_LINE_COLOR (#d9d8d5, exported) — every notion line uses one color
    mobile={boolean | { query?, renderRow? }} // 1.6.0 card list on narrow screens (see "Mobile list")
    scrollParent={HTMLElement | null}         // 1.7.0 virtualize against an outer (page) scroller
    columnResize={boolean | { minWidth? }}    // 1.8.0 drag the header's right edge to resize a column (see "Column resize")
    columnWidths={Record<string, number>}     // 1.8.0 resized widths by column id (you store them)
    onColumnWidthsChange={(widths) => void}   // 1.8.0 fired once on release / on double-click reset
    rowReorder={{ onReorder, canDrag?, disabledReason?, handleWidth?, handleLabel? }} // 1.9.0 drag rows by a handle (see "Row reorder")

    // Optional - 1.4.0 header context menu (right-click on th never shows the browser menu)
    onSortClear={() => void}                // adds "정렬 풀기" to the header menu (and re-picking the active direction clears)
    onHeaderContextMenu={(column, event) => boolean | void} // custom header menu — return true or preventDefault() to skip the built-in one
/>

列选项 (1.2.0)

{ id: "name", text: "이름", width: 160, pinned: "left" }     // sticky left (numeric width needed for the next pinned column)
{ id: "name", text: "이름", primary: true }                  // 15px body text for the row's main cell
{ id: "d1", text: "월", onCellClick: (item, i, e) => {} }    // per-cell click + cell hover (row click is not fired)
{ id: "who", text: "담당", rowSpan: (item, i) => 2 }         // merge down; return 0 on covered rows (use with autoHeight / short tables)
{ id: "m1", text: "1월", cellPadding: "4px 0" }              // body padding override
{ id: "t", text: "업무", cellStyle: (item) => ({ color: "#999" }) } // per-row cell style
{ id: "c", text: "열", headerProps: { draggable: true, onDrop } }  // extra th attributes (drag to reorder, title…)
{ id: "who", text: "책임자", width: 110, colSpan: (item) => (item.isEvent ? 2 : 1) } // 1.3.0: merge right (the covered column is not drawn)
{ id: "title", text: "제목", minWidth: 220 }                // 1.3.0: flexible column (no width) with a minimum — table min width = sum, scrolls sideways

页眉上下文菜单(1.4.0)-右键单击页眉单元格:

// sort items appear for sortable columns when onSort is given
{ id: "name", text: "이름", sortable: true }
// filter checklist (multi by default; filterMultiple: false = pick one)
{
    id: "status", text: "상태",
    filterOptions: [{ value: "open", label: "진행" }, { value: "done", label: "완료" }],
    filterValues: status,                 // string[] — non-empty → header turns primary + filter icon
    onFilterChange: (values) => setStatus(values),
}
{ id: "memo", text: "메모", headerMenu: false } // no menu on this column (browser menu still blocked)
{ id: "due", text: <MyHeader />, sortMenu: true } // 1.4.1: sort items in the menu without VDT's sort label/click (custom-drawn header)

// open your own header menu instead
<VirtualDataTable onHeaderContextMenu={(col, e) => (col.id === "owner" ? (openOwnerMenu(e), true) : false)} … />

共享页眉菜单( 1. 5.0) – 从自定义页眉按钮/ 工具栏按钮中自己打开 :

import { HeaderMenu, headerMenuAnchor } from "@ehfuse/mui-virtual-data-table";

const [pos, setPos] = useState<{ left: number; top: number } | null>(null);
<button onClick={(e) => setPos(headerMenuAnchor(e.currentTarget))}>상태</button>
<HeaderMenu
    open={Boolean(pos)}
    anchorPosition={pos}
    onClose={() => setPos(null)}
    sort={{ active: sortDir, onChange: (d) => setSortDir(d) }}           // null = 풀기
    filter={{
        options: [{ value: "open", label: "진행", icon: <Dot color="green" /> }],
        value: picked, onChange: setPicked,
        multiple: true,                 // false = radio (closes on pick), allLabel: "전체"
        emptyLabel: "비어 있음",        // adds value ""
        searchable: undefined,          // default: > 10 options
    }}
>
    {/* extra items */}
</HeaderMenu>

平接行实例 (1.3. 0) :

<VirtualDataTable
    variant="notion"
    data={rows}                                   // without the pinned ones
    pinnedRows={[{ kind: "band", label: "미확인" }, ...unconfirmed]}
    pinnedRowsMaxHeight={49 * 6}                  // band + 5 rows, then it scrolls
    renderFullWidthRow={(r) => (r.kind === "band" ? r.label : null)}
    getRowId={(r) => r.id}
    rowKeyFromId
    resetScrollOnDataChange={false}
    ...
/>

概念默认 (1. 2. 0): 第48行, 标题文本 13px, 所选行(selectedRowId= (单位:千美元) #dbeafe + 3px 键 #3b82f6 左栏(包含暗值), 字符串 emptyMessage =头下为一浅灰色线条,页脚细胞使用体垫.

页面滚动 (1.7. 0)

scrollParent={element} 虚拟化对照外向滚动器(反应-虚拟) customScrollParent)——该表本身没有卷轴 并随页面而增长,所以捆绑到页面卷轴上的东西(collapsing app bar,滚动到上,拉到再刷)继续工作. 过 null ,而该元素尚不为人所知(在此之前没有绘图)。 与 mobile 财务报告和审定财务报表 onEndReached。 。 。 .

列大小调整(1.8.0)

columnResize 抓住每个信头的右边缘(col-resize 光标,悬浮上的细线). 拖动它来调整列的大小, 双击它,然后回到专栏 width。表格不存储任何东西——将宽度保留在您喜欢的地方 (账户设置, 本地Storage...) 并用 columnWidths没有 columnWidths 桌子只记得他们 直到它重新挂起.

const [widths, setWidths] = useAccountSetting("customer.list"); // Record<columnId, px>
<VirtualDataTable
    columnResize                              // or { minWidth: 60 } — default minimum 48px (a column's own minWidth wins)
    columnWidths={isPhone ? undefined : widths}
    onColumnWidthsChange={setWidths}          // once on release; double-click → the key is removed
    ...
/>
  • 虽然只将 CSS 变量拖入到表格框变化中——行不重递,但虚拟化和水平卷轴保持不变.
  • 拖曳之后的点击被吞噬, 因此释放到一个标题上 永远不要排序 / 打开标题菜单 .
  • 灵活列( 无) width(b) 保持最低限度的 minWidth (或调整最小大小) 一旦任何东西被调整大小; 当每列结束 表格具有像素宽度,宽度与总和一样(没有拉伸使所拉宽与所拉宽不同). 如果没有一列是灵活的,第一个拖放会捕捉到每个一列所绘制的宽度(1.8.1),所以其他一列不会跳出.
  • 与 pinned: "left" (粘贴相冲后), autoHeight, (中文(简体) ), framed, (中文(简体) ), tableLayout, (中文(简体) ), variant="notion", (中文(简体) ), pinnedRows脚印. 在那个 mobile 没有卡片列表 columnWidths 被忽略.

行重排( 1. 9. 0)

rowReorder 添加拖动手柄( QQ) 为第一个单元格 。 拖放以移动行; 显示行将降落在哪里 onReorder(from, to, item) 释放时起火(to 是行尾的索引——与 arrayMove(data, from, to)) (中文(简体) ). 桌子永远不变 data· ; 自己重新订购,传回.

<VirtualDataTable
    rowReorder={{
        onReorder: (from, to) => setRows((r) => arrayMove(r, from, to)),
        disabledReason: sortBy ? "정렬을 풀면 옮길 수 있어요" : undefined, // handle is dimmed, drag is off
        canDrag: (row) => !row.locked,                                    // no handle on locked rows
    }}
    ...
/>
  • 拖曳时没有再退让——被拖走的行得到一个 data-vdt-dragging 属性( dimmed) 和放行是一个位置元素, 因此它保持轻与虚拟化和长列表。 拖入边缘自动滚动(表卷轴, scrollParent,或文档).
  • 键盘:将手柄聚焦于 → 空间(升起) → / (移动一步, onReorder 空间 / 输入 / Esc( 投放) .
  • 在那个 mobile 卡片列表 手柄坐落在卡片左边. 与 pinned: "left" 手柄柱也被固定. 输入行 pinnedRows 无法移动 。 与 autoHeight, (中文(简体) ), scrollParent, (中文(简体) ), variant="notion", (中文(简体) ), columnResize。 。 。 .
  • 除手柄外,行点击仍然在任何地方工作;拖动后点击不作为行点击处理.

移动列表 (1.6.0)

mobile 将表格转换为窄屏幕上的卡片列表(默认查询) (max-width:599.95px):没有标题行,每行一张牌, 虚拟化/ onLoadMore 编号 : onRowClick 编号 : selectedRowId 一样的工作。 上面的菜单、页脚和被绑的行不画在那里.

<VirtualDataTable
    mobile                                   // or { query: "(max-width:899.95px)", renderRow: (item, index) => <MyCard/> }
    columns={[
        { id: "thumb", text: "", mobile: "thumb", render: (r) => <img src={r.image} width={56} /> },
        { id: "title", text: "작품", mobile: "title" },
        { id: "status", text: "상태", mobile: "badge", render: (r) => <Chip label={r.status} /> },
        { id: "artist", text: "작가", mobile: "sub" },
        { id: "price", text: "판매가" },              // no hint → meta line "판매가 1,000,000원"
        { id: "memo", text: "메모", mobile: false },  // hidden on mobile
    ]}
    ...
/>

槽数 : title (粗体一行-默认:第一栏), thumb (左边), badge (标题权), sub (标题下行), meta ("头值"对相接-其他列的默认), false (隐也. mobileLabel 覆盖元标签( n)false = 仅值).

许可证

麦特 金·杨金(原始内容存档于2018-10-21). [email protected](中文(简体) )

捐款

欢迎捐款! 请随意提交拉力请求.

支助

支持, 电子邮件 (原始内容存档于2018-10-21). [email protected] 或者在 GitHub 上创建一个问题.

版本与发布记录

@ehfuse/mui-virtual-data-table 共发布 78 个版本,首次发布于 2025/10/01;过去 12 个月发布了 69 个版本。

版本发布日期距今
1.11.4latest2026/11/0310 天前
1.11.32026/11/0310 天前
1.11.22026/11/0310 天前
1.11.12026/11/0310 天前
1.11.02026/10/3014 天前
1.10.42026/10/2915 天前

关于 @ehfuse/mui-virtual-data-table 的常见问题

@ehfuse/mui-virtual-data-table 是什么?

与 Materi-UI 集成反应的高性能虚拟数据表组件

如何安装 @ehfuse/mui-virtual-data-table?

在项目目录运行 npm install @ehfuse/mui-virtual-data-table,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=16.8.0)、react-dom(>=16.8.0)、@mui/material(>=5.0.0)、@emotion/react(>=11.0.0)、react-virtuoso(>=4.0.0)、@emotion/styled(>=11.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@ehfuse/mui-virtual-data-table 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ehfuse/mui-virtual-data-table 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@ehfuse/mui-virtual-data-table 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=16.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@ehfuse/mui-virtual-data-table 可以商用吗?

@ehfuse/mui-virtual-data-table 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ehfuse/mui-virtual-data-table 的下载量有多少?

根据 npm 官方下载统计,@ehfuse/mui-virtual-data-table 在 2026/10/13 – 2026/11/11 这 30 天内被下载 5,770 次,日均约 192 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ehfuse/mui-virtual-data-table 还在维护吗?

最新版本 1.11.4 发布于 2026/11/03(10 天前);至今共发布 78 个版本,其中过去 12 个月发布了 69 个;首次发布于 2025/10/01。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。