简体中文 | English
基于 FlashList v2 的高性能 React Native 数据表格,支持 iOS、Android 和 Web。内置横纵滚动、固定列、自适应列宽、行选择、受控排序、展开行、异步子表、主题与边框配置。
A high-performance, cross-platform data table for React Native, Expo, and React Native Web.
- FlashList v2 虚拟化,适合大数据列表。
- 纵向滚动、横向滚动与可关闭的吸顶表头。
fixed: 'left' | 'right'左右固定列。- 按真实渲染内容测量自适应列宽,支持
minWidth和定宽列。 - 受控行选择、全选/半选、禁用行、自定义 Checkbox 及选择列合并。
- 受控三态排序:
null → ascend → descend → null。 - 自定义展开面板或异步联动子表。
- 下拉刷新、触底加载、空态、页面级头尾内容。
- 可配置主题、斑马纹、按压高亮、单元格样式和网格边框。
- TypeScript 类型声明随包发布。
| 环境 | 支持范围 |
|---|---|
| Expo | SDK 57 |
| React Native | >=0.78.0 |
| React | >=19.0.0 <20 |
@shopify/flash-list |
>=2.2.1 <3 |
| Node.js | >=22.13.0 |
| 平台 | iOS / Android / Web |
Web 端需要宿主项目已正常配置 React Native Web;Expo 项目默认已包含该能力。
npx expo install @shopify/flash-list
npm install @bestcoder/react-native-data-tablenpm install @bestcoder/react-native-data-table @shopify/flash-list本包没有自定义原生模块;原生端要求取决于 @shopify/flash-list 和宿主 React Native 项目。
DataTable 默认使用 flex: 1,父容器必须有可计算的高度;也可直接传入 height 或 maxHeight。
import { Text, View } from 'react-native';
import {
DataTable,
type TableColumn,
} from '@bestcoder/react-native-data-table';
interface Order {
id: string;
customer: string;
amount: number;
status: 'paid' | 'pending';
}
const rows: Order[] = [
{ id: 'SO-001', customer: 'Alice', amount: 1280, status: 'paid' },
{ id: 'SO-002', customer: 'Bob', amount: 860, status: 'pending' },
];
const columns: TableColumn<Order>[] = [
{ title: '订单号', dataIndex: 'id', width: 110, fixed: 'left' },
{ title: '客户', dataIndex: 'customer', minWidth: 120 },
{ title: '金额', dataIndex: 'amount', align: 'right', sorter: true },
{ title: '状态', dataIndex: 'status', minWidth: 100 },
];
export function OrderList() {
return (
<View style={{ flex: 1 }}>
<DataTable
data={rows}
columns={columns}
keyExtractor={(item) => item.id}
striped
highlightOnRowPress
/>
</View>
);
}组件只输出排序意图,不会修改 data。调用方需根据 currentSort 排序数据后再传回。
const [sort, setSort] = useState<SortParams>();
const sortedRows = useMemo(() => sortRows(rows, sort), [rows, sort]);
<DataTable
data={sortedRows}
columns={columns}
keyExtractor={(item) => item.id}
currentSort={sort}
onSort={setSort}
/>rowSelection 是受控配置。不传时不渲染选择列。
const tableRef = useRef<DataTableHandle>(null);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
<DataTable
ref={tableRef}
data={rows}
columns={columns}
keyExtractor={(item) => item.id}
rowSelection={{
selectedRowKeys: selectedKeys,
onChange: (keys) => setSelectedKeys(keys),
getCheckboxProps: (record) => ({ disabled: record.status === 'paid' }),
position: 'first',
}}
/>
tableRef.current?.selectAll();
tableRef.current?.clearSelection();
tableRef.current?.scrollToTop();custom 是默认模式。展开内容占满当前视口宽度,不跟随主表横向滚动。
<DataTable
data={rows}
columns={columns}
keyExtractor={(item) => item.id}
renderExpandedRow={(record) => (
<View style={{ padding: 16 }}>
<Text>订单:{record.id}</Text>
</View>
)}
/>sub-table 模式先通过 onExpandFetch 获取子数据,再由 getSubTable 返回子表规格。子表没有表头,但可与父表共享横向滚动、固定列和最终列宽。
<DataTable<Order, LineItem[]>
data={rows}
columns={columns}
keyExtractor={(item) => item.id}
expandedRowType="sub-table"
onExpandFetch={(order) => fetchLineItems(order.id)}
getSubTable={(_order, _index, items) => ({
rows: items,
columns: [
{ colIndex: 0, dataIndex: 'sku' },
{ colIndex: 1, dataIndex: 'name' },
{ colIndex: 2, dataIndex: 'quantity', align: 'right' },
],
})}
/>SubColumn.colIndex按父表视觉列顺序对齐,包含内部选择列和固定列归位后的顺序。colSpan可横跨多个父列。- Loading、Error 和 Empty 有默认面板,均可自定义。
subTableSyncScroll和subTableSyncFixedColumns默认都为true。
theme 接受 Partial<DataTableTheme>,与内置主题合并。只需覆盖要修改的 token。
<DataTable
data={rows}
columns={columns}
keyExtractor={(item) => item.id}
theme={{
primary: '#6D5EF7',
primaryLight: '#EFEDFF',
headerBg: '#F8F7FF',
}}
/>| Token | 用途 |
|---|---|
headerBg |
表头、展开区和斑马纹奇数行默认背景 |
rowBg |
数据行、Checkbox 和斑马纹偶数行默认背景 |
text |
主文字 |
textSecondary |
次级文字、错误和空态 |
primary |
Checkbox、排序激活态和 Loading |
primaryLight |
默认行按压高亮 |
onPrimary |
主色上的对勾/半选标记 |
line |
默认边框颜色 |
disabled |
Checkbox 和排序禁用/未激活态 |
也可导入 DEFAULT_DATA_TABLE_THEME 作为完整基线。
<DataTable
data={rows}
columns={columns}
keyExtractor={(item) => item.id}
border={{
horizontal: true,
vertical: true,
outer: true,
color: '#D7D9E0',
width: 1,
}}
/>- 省略
border:只开启默认横向分隔线。 false:关闭全部边框。true:开启默认横向分隔线。- 对象:通过
horizontal、vertical、outer、color和width组合配置。
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
data |
T[] |
必填 | 原始数据源 |
columns |
TableColumn<T>[] |
必填 | 列配置 |
keyExtractor |
(item, index) => string |
必填 | 返回全局唯一且稳定的行 key |
remeasureKey |
string | number |
— | 变化时原地重新测量自适应列 |
height |
DimensionValue |
— | 固定表格高度,优先于 maxHeight |
maxHeight |
DimensionValue |
— | 父 flex 布局中的最大高度 |
striped |
boolean |
false |
开启斑马纹 |
stripeColors |
[string, string] |
[theme.rowBg, theme.headerBg] |
偶数/奇数行背景 |
highlightOnRowPress |
boolean |
false |
按下行时高亮 |
highlightColor |
string |
theme.primaryLight |
行按压高亮色 |
rowSelection |
RowSelectionConfig<T> |
— | 受控行选择配置;开关选择模式或切换合并宿主列时会自动原地重测列宽 |
stickyHeader |
boolean |
true |
表头吸顶开关 |
ref |
Ref<DataTableHandle> |
— | 命令式句柄:行选择与纵/横向滚动方法,见 DataTableHandle |
onSort |
(params: SortParams) => void |
— | 排序状态变化回调 |
currentSort |
SortParams |
— | 当前受控排序状态 |
renderSortIcon |
(order) => ReactNode |
— | 全局自定义排序图标,三态宽度应保持一致 |
refreshing |
boolean |
— | 下拉刷新状态 |
onRefresh |
() => void |
— | 下拉刷新回调 |
onEndReached |
() => void |
— | 触底加载回调 |
onEndReachedThreshold |
number |
— | FlashList 触底阈值 |
ListHeaderComponent |
ComponentType | ReactElement | null |
— | 只跟随纵向滚动的页面级头部 |
ListFooterComponent |
ComponentType | ReactElement | null |
— | 只跟随纵向滚动的页面级尾部 |
ListEmptyComponent |
ComponentType | ReactElement | null |
— | data 为空时渲染 |
expandedRowType |
'custom' | 'sub-table' |
'custom' |
展开行模式 |
expandedRowStyle |
StyleProp<ViewStyle> |
— | 展开区容器样式 |
renderExpandedRow |
(record, index, columnWidths, subData?) => ReactNode |
— | 自定义展开内容 |
onExpandFetch |
(record, index) => Promise<D> |
— | 展开时异步获取数据,按行 key 缓存 |
getSubTable |
(record, index, subData) => SubTableSpec |
— | 将子数据转换为子表规格 |
isExpandedDataEmpty |
(subData) => boolean |
内置判空 | 自定义子数据判空 |
renderExpandedLoading |
(columnWidths) => ReactNode |
内置 Loading | 自定义加载面板 |
renderExpandedError |
(error, retry, columnWidths) => ReactNode |
内置可重试错误面板 | 自定义错误面板 |
renderExpandedEmpty |
(columnWidths) => ReactNode |
内置空面板 | 自定义空数据面板 |
subTableSyncScroll |
boolean |
true |
子表与父表横向滚动联动 |
subTableSyncFixedColumns |
boolean |
true |
子表同步父表固定列 |
expandAnimationDuration |
number |
200 |
展开/折叠过渡动画时长(ms),传 0 关闭动画 |
onRowPress |
(record, index) => void |
— | 行点击回调(横向拖动不触发);与展开行共存时先回调再切换展开 |
style |
StyleProp<ViewStyle> |
— | 最外层容器样式 |
headerStyle |
StyleProp<ViewStyle> |
— | 表头行样式 |
headerTextStyle |
StyleProp<TextStyle> |
— | 全局表头文字样式 |
rowStyle |
StyleProp<ViewStyle> |
— | 数据行样式 |
cellStyle |
StyleProp<ViewStyle> |
— | 全局单元格基础样式 |
cellTextStyle |
StyleProp<TextStyle> |
— | 默认单元格文字样式 |
border |
boolean | DataTableBorder |
横向分隔线 | 边框配置 |
theme |
Partial<DataTableTheme> |
内置主题 | 覆盖主题 token |
T 是父行数据类型,D 是 onExpandFetch 返回的子数据类型,默认为 unknown。
| 字段 | 类型 | 说明 |
|---|---|---|
title |
string |
默认表头标题 |
dataIndex |
keyof T | string |
无 render 时用于读取单元格值 |
width |
number |
固定列宽,跳过内容测量和弹性分配 |
minWidth |
number |
自适应列的最小宽度 |
align |
'left' | 'center' | 'right' |
单元格和表头对齐,默认 left |
fixed |
'left' | 'right' |
左/右固定列 |
sorter |
boolean |
启用表头排序交互 |
cellStyle |
StyleProp<ViewStyle> |
列样式,同时影响表头和数据单元格 |
renderCellStyle |
(record, index) => StyleProp<ViewStyle> |
数据单元格动态样式,优先级最高 |
render |
(record, index) => ReactNode |
自定义数据单元格 |
renderHeader |
() => ReactNode |
自定义表头内容,优先于 title |
headerCellStyle |
StyleProp<ViewStyle> |
仅表头单元格样式 |
headerTextStyle |
StyleProp<TextStyle> |
仅默认表头文字样式 |
样式优先级:
- 数据单元格:内置基础样式 → 全局
cellStyle→ 列cellStyle→renderCellStyle。 - 表头单元格:内置基础样式 → 全局
cellStyle→ 列cellStyle→headerCellStyle。 - 表头文字:内置文字样式 → 全局
headerTextStyle→ 列headerTextStyle。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
selectedRowKeys |
string[] |
必填 | 当前选中 key |
onChange |
(keys, rows) => void |
必填 | 按 data 顺序返回 key 和行数据 |
getCheckboxProps |
(record) => { disabled?: boolean } |
— | 按行禁用选择 |
position |
'first' | 'last' |
'first' |
独立选择列位置,自动归入对应固定列组 |
renderCheckbox |
(selected, record) => ReactNode |
内置 Checkbox | 自定义行选择框内容 |
renderHeaderCheckbox |
({ checked, indeterminate, disabled }) => ReactNode |
内置 Checkbox | 自定义全选/半选内容 |
mergeIntoDataIndex |
string |
— | 将选择框合并进指定 dataIndex 列 |
checkboxAlign |
'top' | 'center' | 'bottom' |
'center' |
合并模式的垂直对齐 |
SubColumn<S>:
| 字段 | 类型 | 说明 |
|---|---|---|
colIndex |
number |
对齐父表视觉列顺序的下标 |
colSpan |
number |
横跨父列数,默认 1 |
dataIndex |
keyof S | string |
无 render 时读取子行字段 |
align |
'left' | 'center' | 'right' |
默认继承对应父列 |
cellStyle |
StyleProp<ViewStyle> |
子列基础样式 |
renderCellStyle |
(row, index) => StyleProp<ViewStyle> |
子单元格动态样式 |
render |
(row, index) => ReactNode |
自定义子单元格 |
SubTableSpec<S>:
| 字段 | 类型 | 说明 |
|---|---|---|
rows |
S[] |
子表行数据 |
columns |
SubColumn<S>[] |
子列配置 |
rowStyle |
StyleProp<ViewStyle> |
子表行样式 |
cellStyle |
StyleProp<ViewStyle> |
全局子单元格样式 |
cellTextStyle |
StyleProp<TextStyle> |
默认子单元格文字样式 |
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color |
string |
theme.line |
边框颜色 |
width |
number |
StyleSheet.hairlineWidth |
边框宽度 |
horizontal |
boolean |
true |
行分隔线 |
vertical |
boolean |
false |
列分隔线 |
outer |
boolean |
false |
表格外边框 |
DataTableHandle:
| 方法 | 说明 |
|---|---|
selectAll |
全选所有未禁用行;已选中的禁用行保持不变 |
clearSelection |
清空可选行;禁用行的已选状态保留 |
scrollToTop(animated?) |
纵向滚动回顶部;适用于筛选后数据变短导致滚动偏移越界的场景 |
scrollToIndex(index, animated?) |
纵向滚动到指定数据行;下标基于当前已上屏数据(测绘扣留中的行不计入) |
scrollToEnd(animated?) |
纵向滚动到底部 |
scrollToLeft |
横向滚动归零,表头与所有行同步回到最左侧 |
SortParams:
| 字段 | 类型 | 说明 |
|---|---|---|
dataIndex |
string |
排序列的 dataIndex |
order |
'ascend' | 'descend' | null |
目标排序方向;null 表示取消排序 |
export { DataTable, DEFAULT_DATA_TABLE_THEME };
export type {
DataTableBorder,
DataTableHandle,
DataTableProps,
DataTableTheme,
RowSelectionConfig,
SortParams,
SubColumn,
SubTableSpec,
TableColumn,
};keyExtractor必须返回全局唯一、稳定的字符串,不要使用可变数组下标。- 未传
height/maxHeight时,父容器必须有明确高度。 - 排序、行选择均为受控能力,组件不会自动排序
data或持久化选中状态。 - 自定义
render、renderHeader和 Checkbox 的各个状态应尽量保持稳定尺寸,避免列宽抖动。 - 自适应列会真实挂载内容进行离屏测量;高成本自定义单元格建议设置明确
width或简化测量期渲染。 onEndReached在首次测量或原地重测期间会被暂时屏蔽,以避免重复翻页。- 子表的
colIndex是父表最终视觉列顺序,不一定等于原始columns下标。 - Web 端的固定列依赖 React Native Web 对 CSS
position: sticky的支持。
MIT © bestcoder。标准许可证文本已包含在 npm 包的 LICENSE 文件中;版本变化参见 CHANGELOG.md。