Skip to content

Latest commit

 

History

History
178 lines (148 loc) · 7.65 KB

17-flexrender.md

File metadata and controls

178 lines (148 loc) · 7.65 KB

FlexRender:Table 渲染入口与单元格上下文

模块: 03-tanstack-stack | 类型: 字典条目(可独立查阅,按主题准备前置知识,支持任意跳入查阅)

📌 定义

FlexRender(组件形式)/ flexRender(函数形式)是 Table 把列定义里的 header/cell/footer 渲染物(string | JSX | 函数组件)落到 React 节点的通用入口。v9 起它脱离 createTableHook 变为直接可导入的顶层 API,且实例上还挂了 table.FlexRender 便捷形态。渲染单元格时拿到的 CellContext(即 cell.getContext())携带 getValue/row/column/table 等完整上下文。

📖 语法 / 签名

// 组件形式:cell / header / footer 三个 prop 三选一(类型为判别联合,其余 prop 是 never)
const headerNode = <FlexRender header={header} />; // 渲染 header.column.columnDef.header
const cellNode = <FlexRender cell={cell} />; // 渲染 cell.column.columnDef.cell
const footerNode = <FlexRender footer={footer} />; // 渲染 footer.column.columnDef.footer

// 函数形式(组件形式的底层):
flexRender(cell.column.columnDef.cell, cell.getContext())
flexRender(header.column.columnDef.header, header.getContext())

// 实例便捷形态(等价组件形式,无需再导入):
const instanceNode = <table.FlexRender cell={cell} />;
CellContext 成员 说明
getValue<T>() 当前列的原始值(配合泛型收窄)
renderValue() 读取 accessor 值,并在 null/undefined 时使用配置的 renderFallbackValue
row 行对象:row.original 原始数据、row.id、row.getVisibleCells()
column 列实例:column.id、columnDef、排序/筛选等 feature 方法
cell / table 单元格与表实例
渲染位 前提
header.isPlaceholder 分组列的占位 header,为 true 时渲染 null
header.colSpan 表头 <th> 必须透传,否则列对不齐
row.getVisibleCells() 需要 columnVisibilityFeature(core 行只有 getAllCells())

💡 示例

import {
  useTable, tableFeatures, rowSortingFeature, columnVisibilityFeature,
  createColumnHelper, FlexRender, flexRender, createSortedRowModel,
} from '@tanstack/react-table'

const features = tableFeatures({
  rowSortingFeature,       // getToggleSortingHandler 来自排序特性
  sortedRowModel: createSortedRowModel(), // 让本地行顺序响应排序状态
  columnVisibilityFeature, // getVisibleCells 需要此特性
})
const columnHelper = createColumnHelper<typeof features, Person>()

const columns = columnHelper.columns([
  {
    accessorKey: 'name',
    header: '姓名',
    // CellContext:info.getValue / info.renderValue / info.row / info.column / info.table
    cell: (info) => <b>{info.getValue<string>()}</b>,
  },
  {
    accessorKey: 'age',
    header: ({ column }) => (   // header 也可以是函数,拿 column 上下文
      <button onClick={column.getToggleSortingHandler()}>年龄</button>
    ),
  },
])

// 组件形式:全表渲染
function FlexTable() {
  const table = useTable({ data: people, columns, features })
  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((headerGroup) => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <th key={header.id} colSpan={header.colSpan}>
                {header.isPlaceholder ? null : <FlexRender header={header} />}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map((row) => (
          <tr key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <td key={cell.id}>
                <FlexRender cell={cell} />
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  )
}

// 函数形式:headless 完全自定义;core 场景(无可见性特性)用 getAllCells
function FlexRenderFnTable() {
  const coreFeatures = tableFeatures({})
  const coreHelper = createColumnHelper<typeof coreFeatures, Person>()
  const coreColumns = coreHelper.columns([
    { accessorKey: 'name', header: '姓名', cell: (info) => info.getValue<string>() },
  ])
  const table = useTable({ data: people, columns: coreColumns, features: coreFeatures })
  return (
    <tbody>
      {table.getRowModel().rows.map((row) => (
        <tr key={row.id}>
          {row.getAllCells().map((cell) => (
            <td key={cell.id}>
              {flexRender(cell.column.columnDef.cell, cell.getContext())}
            </td>
          ))}
        </tr>
      ))}
    </tbody>
  )
}

// 实例便捷形态
function InstanceFlexTable() {
  const table = useTable({ data: people, columns, features })
  return (
    <tbody>
      {table.getRowModel().rows.map((row) => (
        <tr key={row.id}>
          {row.getVisibleCells().map((cell) => (
            <td key={cell.id}>
              const instanceNode = <table.FlexRender cell={cell} />;
            </td>
          ))}
        </tr>
      ))}
    </tbody>
  )
}

⚠️ 常见陷阱

  • ❌ 直接渲染 {cell.getValue()}:列定义里写的是 JSX/组件时不会被执行——凡自定义渲染一律过 FlexRender/flexRender
  • ❌ <FlexRender cell={cell} header={header} /> 同传:类型判别联合直接报错,一次只渲一个渲染位
  • ❌ 表头漏 header.isPlaceholder 判断与 colSpan:分组表头错位、多余空 <th>
  • ❌ core 场景调 row.getVisibleCells():没有 columnVisibilityFeature 时该成员不存在,用 row.getAllCells()
  • ❌ 以为 FlexRender 只能配 createTableHook 生成的表:顶层导入即可用于任何 useTable 实例
  • ✅ 函数形式适合封装自定义 <CellRenderer>,组件形式/实例形式适合直接写表体

取值、格式化与创建 React 节点

先修:Table 列定义、React 组件。getValue 取 accessor 产生的值;renderValue 在缺值时应用回退;FlexRender 执行列的渲染模板。它们不是三个名字不同的同一函数。

例如原数据 {price: 1200} 的 accessor 仍返回数字 1200,cell 可以显示 ¥12.00。排序基于数字,展示基于 cell。直接输出 getValue 会跳过这个格式化模板,但简单原始文本展示并非禁止。

示例中的 Person、people 需要先定义;函数返回 tbody 的版本必须放在 table 内使用。点表头只改变排序状态还不够,本地排序需要注册 sortedRowModel,服务端排序则需要请求对应新数据。

练习: 增加金额列,让原值与显示值明显不同,分别用 getValue 与 FlexRender 观察。再启用分组表头,确认 colSpan 与 placeholder 不产生错位。验收:能说明排序依据,键盘也能触发表头按钮。参考FlexRender 指南。

🔗 相关条目


最后更新: 2026年9月 | 本条目为模块知识字典的一部分,概念完整解释以此处为单一事实来源

阅读导航

本模块理解地图 · 完整目录与版本 · 通用术语