Framework
Version
Enterprise

Benutzerdefinierte Features Guide

Beispiele

Möchten Sie direkt zur Implementierung springen? Sehen Sie sich diese Beispiele an

Benutzerdefinierte Features Guide

In diesem Leitfaden erfahren Sie, wie Sie TanStack Table mit benutzerdefinierten Features erweitern und lernen dabei mehr über die Struktur und Funktionsweise der TanStack Table v8 Codebasis.

TanStack Table strebt danach, schlank zu sein

TanStack Table verfügt über einen Kernsatz von Features, die in die Bibliothek integriert sind, wie z. B. Sortierung, Filterung, Paginierung usw. Wir haben viele Anfragen und manchmal auch gut durchdachte PRs erhalten, um weitere Features in die Bibliothek aufzunehmen. Obwohl wir stets offen für die Verbesserung der Bibliothek sind, möchten wir auch sicherstellen, dass TanStack Table eine schlanke Bibliothek bleibt, die nicht zu viel Ballast und Code enthält, der in den meisten Anwendungsfällen unwahrscheinlich verwendet wird. Nicht jeder PR kann oder sollte in die Kernbibliothek aufgenommen werden, auch wenn er ein echtes Problem löst. Dies kann für Entwickler frustrierend sein, deren Anwendungsfall TanStack Table zu 90 % abdeckt, sie aber etwas mehr Kontrolle benötigen.

TanStack Table war schon immer so aufgebaut, dass es hochgradig erweiterbar ist (zumindest seit v7). Die table-Instanz, die von jedem Framework-Adapter zurückgegeben wird, den Sie verwenden (useReactTable, useVueTable usw.), ist ein einfacher JavaScript-Objekt, dem zusätzliche Eigenschaften oder APIs hinzugefügt werden können. Es war schon immer möglich, Komposition zu verwenden, um benutzerdefinierte Logik, Zustände und APIs zur Tabelleninstanz hinzuzufügen. Bibliotheken wie Material React Table haben einfach benutzerdefinierte Wrapper-Hooks um den useReactTable-Hook erstellt, um die Tabelleninstanz mit benutzerdefinierten Funktionalitäten zu erweitern.

Ab Version 8.14.0 hat TanStack Table jedoch eine neue _features-Tabellenoption verfügbar gemacht, die es Ihnen ermöglicht, benutzerdefinierten Code enger und sauberer in die Tabelleninstanz zu integrieren, und zwar genau auf die gleiche Weise wie die integrierten Tabellen-Features.

TanStack Table v8.14.0 hat eine neue _features-Option eingeführt, die es Ihnen ermöglicht, benutzerdefinierte Features zur Tabelleninstanz hinzuzufügen.

Mit dieser neuen engeren Integration können Sie einfach komplexere benutzerdefinierte Features zu Ihren Tabellen hinzufügen und sie möglicherweise sogar bündeln und mit der Community teilen. Wir werden sehen, wie sich dies im Laufe der Zeit entwickelt. In einer zukünftigen v9-Version könnten wir sogar die Bundle-Größe von TanStack Table reduzieren, indem wir alle Features optional machen, aber das wird noch erforscht.

Wie TanStack Table Features funktionieren

Der Quellcode von TanStack Table ist wohl eher einfach (zumindest finden wir das). Der gesamte Code für jedes Feature ist in einem eigenen Objekt/Datei aufgeteilt, mit Instanziierungsmethoden zur Erstellung von Anfangszuständen, Standard-Tabellen- und Spaltenoptionen sowie API-Methoden, die zu den table-, header-, column-, row- und cell-Instanzen hinzugefügt werden können.

Die gesamte Funktionalität eines Feature-Objekts kann durch den TableFeature-Typ beschrieben werden, der von TanStack Table exportiert wird. Dieser Typ ist eine TypeScript-Schnittstelle, die die Form eines Feature-Objekts beschreibt, das zur Erstellung eines Features benötigt wird.

ts
export interface TableFeature<TData extends RowData = any> {
  createCell?: (
    cell: Cell<TData, unknown>,
    column: Column<TData>,
    row: Row<TData>,
    table: Table<TData>
  ) => void
  createColumn?: (column: Column<TData, unknown>, table: Table<TData>) => void
  createHeader?: (header: Header<TData, unknown>, table: Table<TData>) => void
  createRow?: (row: Row<TData>, table: Table<TData>) => void
  createTable?: (table: Table<TData>) => void
  getDefaultColumnDef?: () => Partial<ColumnDef<TData, unknown>>
  getDefaultOptions?: (
    table: Table<TData>
  ) => Partial<TableOptionsResolved<TData>>
  getInitialState?: (initialState?: InitialTableState) => Partial<TableState>
}
export interface TableFeature<TData extends RowData = any> {
  createCell?: (
    cell: Cell<TData, unknown>,
    column: Column<TData>,
    row: Row<TData>,
    table: Table<TData>
  ) => void
  createColumn?: (column: Column<TData, unknown>, table: Table<TData>) => void
  createHeader?: (header: Header<TData, unknown>, table: Table<TData>) => void
  createRow?: (row: Row<TData>, table: Table<TData>) => void
  createTable?: (table: Table<TData>) => void
  getDefaultColumnDef?: () => Partial<ColumnDef<TData, unknown>>
  getDefaultOptions?: (
    table: Table<TData>
  ) => Partial<TableOptionsResolved<TData>>
  getInitialState?: (initialState?: InitialTableState) => Partial<TableState>
}

Das mag etwas verwirrend sein, lassen Sie uns also aufschlüsseln, was jede dieser Methoden tut

Standardoptionen und Anfangszustand


getDefaultOptions

Die Methode getDefaultOptions in einem Tabellen-Feature ist für die Festlegung der Standard-Tabellenoptionen für dieses Feature verantwortlich. Zum Beispiel legt die Methode getDefaultOptions im Feature Column Sizing die Standardoption columnResizeMode mit dem Standardwert "onEnd" fest.


getDefaultColumnDef

Die Methode getDefaultColumnDef in einem Tabellen-Feature ist für die Festlegung der Standard-Spaltenoptionen für dieses Feature verantwortlich. Zum Beispiel legt die Methode getDefaultColumnDef im Feature Sorting die Standard-Spaltenoption sortUndefined mit dem Standardwert 1 fest.


getInitialState

Die Methode getInitialState in einem Tabellen-Feature ist für die Festlegung des Standardzustands für dieses Feature verantwortlich. Zum Beispiel legt die Methode getInitialState im Feature Pagination den Standardzustand pageSize mit dem Wert 10 und den Standardzustand pageIndex mit dem Wert 0 fest.

API-Ersteller


createTable

Die Methode createTable in einem Tabellen-Feature ist für das Hinzufügen von Methoden zur table-Instanz verantwortlich. Zum Beispiel fügt die Methode createTable im Feature Row Selection viele API-Methoden für die Tabelleninstanz hinzu, wie z. B. toggleAllRowsSelected, getIsAllRowsSelected, getIsSomeRowsSelected usw. Wenn Sie also table.toggleAllRowsSelected() aufrufen, rufen Sie eine Methode auf, die von dem Feature RowSelection zur Tabelleninstanz hinzugefügt wurde.


createHeader

Die Methode createHeader in einem Tabellen-Feature ist für das Hinzufügen von Methoden zur header-Instanz verantwortlich. Zum Beispiel fügt die Methode createHeader im Feature Column Sizing viele API-Methoden für die Header-Instanz hinzu, wie z. B. getStart und viele andere. Wenn Sie also header.getStart() aufrufen, rufen Sie eine Methode auf, die von dem Feature ColumnSizing zur Header-Instanz hinzugefügt wurde.


createColumn

Die Methode createColumn in einem Tabellen-Feature ist für das Hinzufügen von Methoden zur column-Instanz verantwortlich. Zum Beispiel fügt die Methode createColumn im Feature Sorting viele API-Methoden für die Spalteninstanz hinzu, wie z. B. getNextSortingOrder, toggleSorting usw. Wenn Sie also column.toggleSorting() aufrufen, rufen Sie eine Methode auf, die von dem Feature RowSorting zur Spalteninstanz hinzugefügt wurde.


createRow

Die Methode createRow in einem Tabellen-Feature ist für das Hinzufügen von Methoden zur row-Instanz verantwortlich. Zum Beispiel fügt die Methode createRow im Feature Row Selection viele API-Methoden für die Zeileninstanz hinzu, wie z. B. toggleSelected, getIsSelected usw. Wenn Sie also row.toggleSelected() aufrufen, rufen Sie eine Methode auf, die von dem Feature RowSelection zur Zeileninstanz hinzugefügt wurde.


createCell

Die Methode createCell in einem Tabellen-Feature ist für das Hinzufügen von Methoden zur cell-Instanz verantwortlich. Zum Beispiel fügt die Methode createCell im Feature Column Grouping viele API-Methoden für die Zelleninstanz hinzu, wie z. B. getIsGrouped, getIsAggregated usw. Wenn Sie also cell.getIsGrouped() aufrufen, rufen Sie eine Methode auf, die von dem Feature ColumnGrouping zur Zelleninstanz hinzugefügt wurde.

Hinzufügen eines benutzerdefinierten Features

Lassen Sie uns ein benutzerdefiniertes Tabellen-Feature für einen hypothetischen Anwendungsfall durchgehen. Nehmen wir an, wir möchten der Tabelleninstanz ein Feature hinzufügen, das es dem Benutzer ermöglicht, die "Dichte" (Abstand der Zellen) der Tabelle zu ändern.

Schauen Sie sich das vollständige custom-features-Beispiel an, um die vollständige Implementierung zu sehen, aber hier ist ein detaillierter Blick auf die Schritte zur Erstellung eines benutzerdefinierten Features.

Schritt 1: TypeScript-Typen einrichten

Unter der Annahme, dass Sie die gleiche vollständige Typsicherheit wünschen wie die integrierten Features in TanStack Table, richten wir alle TypeScript-Typen für unser neues Feature ein. Wir erstellen Typen für neue Tabellenoptionen, Zustände und Tabelleninstanz-API-Methoden.

Diese Typen folgen der Namenskonvention, die intern in TanStack Table verwendet wird, aber Sie können sie benennen, wie Sie möchten. Wir fügen diese Typen TanStack Table noch nicht hinzu, das machen wir im nächsten Schritt.

ts
// define types for our new feature's custom state
export type DensityState = 'sm' | 'md' | 'lg'
export interface DensityTableState {
  density: DensityState
}

// define types for our new feature's table options
export interface DensityOptions {
  enableDensity?: boolean
  onDensityChange?: OnChangeFn<DensityState>
}

// Define types for our new feature's table APIs
export interface DensityInstance {
  setDensity: (updater: Updater<DensityState>) => void
  toggleDensity: (value?: DensityState) => void
}
// define types for our new feature's custom state
export type DensityState = 'sm' | 'md' | 'lg'
export interface DensityTableState {
  density: DensityState
}

// define types for our new feature's table options
export interface DensityOptions {
  enableDensity?: boolean
  onDensityChange?: OnChangeFn<DensityState>
}

// Define types for our new feature's table APIs
export interface DensityInstance {
  setDensity: (updater: Updater<DensityState>) => void
  toggleDensity: (value?: DensityState) => void
}

Schritt 2: Declaration Merging verwenden, um neue Typen zu TanStack Table hinzuzufügen

Wir können TypeScript anweisen, die von TanStack Table exportierten Typen zu ändern, um unsere neuen Feature-Typen einzuschließen. Dies wird als "Declaration Merging" bezeichnet und ist ein mächtiges Feature von TypeScript. Auf diese Weise müssen wir keine TypeScript-Hacks wie as unknown as CustomTable oder // @ts-ignore in unserem neuen Feature-Code oder in unserem Anwendungscode verwenden.

ts
// Use declaration merging to add our new feature APIs and state types to TanStack Table's existing types.
declare module '@tanstack/react-table' { // or whatever framework adapter you are using
  //merge our new feature's state with the existing table state
  interface TableState extends DensityTableState {}
  //merge our new feature's options with the existing table options
  interface TableOptionsResolved<TData extends RowData>
    extends DensityOptions {}
  //merge our new feature's instance APIs with the existing table instance APIs
  interface Table<TData extends RowData> extends DensityInstance {}
  // if you need to add cell instance APIs...
  // interface Cell<TData extends RowData, TValue> extends DensityCell
  // if you need to add row instance APIs...
  // interface Row<TData extends RowData> extends DensityRow
  // if you need to add column instance APIs...
  // interface Column<TData extends RowData, TValue> extends DensityColumn
  // if you need to add header instance APIs...
  // interface Header<TData extends RowData, TValue> extends DensityHeader

  // Note: declaration merging on `ColumnDef` is not possible because it is a complex type, not an interface.
  // But you can still use declaration merging on `ColumnDef.meta`
}
// Use declaration merging to add our new feature APIs and state types to TanStack Table's existing types.
declare module '@tanstack/react-table' { // or whatever framework adapter you are using
  //merge our new feature's state with the existing table state
  interface TableState extends DensityTableState {}
  //merge our new feature's options with the existing table options
  interface TableOptionsResolved<TData extends RowData>
    extends DensityOptions {}
  //merge our new feature's instance APIs with the existing table instance APIs
  interface Table<TData extends RowData> extends DensityInstance {}
  // if you need to add cell instance APIs...
  // interface Cell<TData extends RowData, TValue> extends DensityCell
  // if you need to add row instance APIs...
  // interface Row<TData extends RowData> extends DensityRow
  // if you need to add column instance APIs...
  // interface Column<TData extends RowData, TValue> extends DensityColumn
  // if you need to add header instance APIs...
  // interface Header<TData extends RowData, TValue> extends DensityHeader

  // Note: declaration merging on `ColumnDef` is not possible because it is a complex type, not an interface.
  // But you can still use declaration merging on `ColumnDef.meta`
}

Wenn wir dies korrekt tun, sollten wir keine TypeScript-Fehler haben, wenn wir sowohl unseren neuen Feature-Code erstellen als auch ihn in unserer Anwendung verwenden.

Vorbehalte bei der Verwendung von Declaration Merging

Ein Vorbehalt bei der Verwendung von Declaration Merging ist, dass es sich auf die TanStack Table-Typen für jede Tabelle in Ihrer Codebasis auswirkt. Dies ist kein Problem, wenn Sie planen, für jede Tabelle in Ihrer Anwendung das gleiche Feature-Set zu laden, aber es könnte ein Problem sein, wenn einige Ihrer Tabellen zusätzliche Features laden und andere nicht. Alternativ können Sie einfach eine Reihe von benutzerdefinierten Typen erstellen, die von den TanStack Table-Typen mit Ihren neuen Features erweitert werden. Dies tut Material React Table, um die Typen von Vanilla TanStack Table-Tabellen nicht zu beeinträchtigen, ist aber etwas mühsamer und erfordert an bestimmten Stellen viel Typ-Casting.

Schritt 3: Das Feature-Objekt erstellen

Nachdem all diese TypeScript-Einrichtung abgeschlossen ist, können wir nun das Feature-Objekt für unser neues Feature erstellen. Hier definieren wir alle Methoden, die der Tabelleninstanz hinzugefügt werden.

Verwenden Sie den TableFeature-Typ, um sicherzustellen, dass Sie das Feature-Objekt korrekt erstellen. Wenn die TypeScript-Typen korrekt eingerichtet sind, sollten Sie keine TypeScript-Fehler haben, wenn Sie das Feature-Objekt mit dem neuen Zustand, den Optionen und den Instanz-APIs erstellen.

ts
export const DensityFeature: TableFeature<any> = { //Use the TableFeature type!!
  // define the new feature's initial state
  getInitialState: (state): DensityTableState => {
    return {
      density: 'md',
      ...state,
    }
  },

  // define the new feature's default options
  getDefaultOptions: <TData extends RowData>(
    table: Table<TData>
  ): DensityOptions => {
    return {
      enableDensity: true,
      onDensityChange: makeStateUpdater('density', table),
    } as DensityOptions
  },
  // if you need to add a default column definition...
  // getDefaultColumnDef: <TData extends RowData>(): Partial<ColumnDef<TData>> => {
  //   return { meta: {} } //use meta instead of directly adding to the columnDef to avoid typescript stuff that's hard to workaround
  // },

  // define the new feature's table instance methods
  createTable: <TData extends RowData>(table: Table<TData>): void => {
    table.setDensity = updater => {
      const safeUpdater: Updater<DensityState> = old => {
        let newState = functionalUpdate(updater, old)
        return newState
      }
      return table.options.onDensityChange?.(safeUpdater)
    }
    table.toggleDensity = value => {
      table.setDensity(old => {
        if (value) return value
        return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' //cycle through the 3 options
      })
    }
  },

  // if you need to add row instance APIs...
  // createRow: <TData extends RowData>(row, table): void => {},
  // if you need to add cell instance APIs...
  // createCell: <TData extends RowData>(cell, column, row, table): void => {},
  // if you need to add column instance APIs...
  // createColumn: <TData extends RowData>(column, table): void => {},
  // if you need to add header instance APIs...
  // createHeader: <TData extends RowData>(header, table): void => {},
}
export const DensityFeature: TableFeature<any> = { //Use the TableFeature type!!
  // define the new feature's initial state
  getInitialState: (state): DensityTableState => {
    return {
      density: 'md',
      ...state,
    }
  },

  // define the new feature's default options
  getDefaultOptions: <TData extends RowData>(
    table: Table<TData>
  ): DensityOptions => {
    return {
      enableDensity: true,
      onDensityChange: makeStateUpdater('density', table),
    } as DensityOptions
  },
  // if you need to add a default column definition...
  // getDefaultColumnDef: <TData extends RowData>(): Partial<ColumnDef<TData>> => {
  //   return { meta: {} } //use meta instead of directly adding to the columnDef to avoid typescript stuff that's hard to workaround
  // },

  // define the new feature's table instance methods
  createTable: <TData extends RowData>(table: Table<TData>): void => {
    table.setDensity = updater => {
      const safeUpdater: Updater<DensityState> = old => {
        let newState = functionalUpdate(updater, old)
        return newState
      }
      return table.options.onDensityChange?.(safeUpdater)
    }
    table.toggleDensity = value => {
      table.setDensity(old => {
        if (value) return value
        return old === 'lg' ? 'md' : old === 'md' ? 'sm' : 'lg' //cycle through the 3 options
      })
    }
  },

  // if you need to add row instance APIs...
  // createRow: <TData extends RowData>(row, table): void => {},
  // if you need to add cell instance APIs...
  // createCell: <TData extends RowData>(cell, column, row, table): void => {},
  // if you need to add column instance APIs...
  // createColumn: <TData extends RowData>(column, table): void => {},
  // if you need to add header instance APIs...
  // createHeader: <TData extends RowData>(header, table): void => {},
}

Schritt 4: Das Feature zur Tabelle hinzufügen

Jetzt, da wir unser Feature-Objekt haben, können wir es zur Tabelleninstanz hinzufügen, indem wir es der Option _features übergeben, wenn wir die Tabelleninstanz erstellen.

ts
const table = useReactTable({
  _features: [DensityFeature], //pass the new feature to merge with all of the built-in features under the hood
  columns,
  data,
  //..
})
const table = useReactTable({
  _features: [DensityFeature], //pass the new feature to merge with all of the built-in features under the hood
  columns,
  data,
  //..
})

Schritt 5: Das Feature in Ihrer Anwendung verwenden

Nun, da das Feature zur Tabelleninstanz hinzugefügt wurde, können Sie die neuen Instanz-APIs, Optionen und Zustände in Ihrer Anwendung verwenden.

tsx
const table = useReactTable({
  _features: [DensityFeature], //pass our custom feature to the table to be instantiated upon creation
  columns,
  data,
  //...
  state: {
    density, //passing the density state to the table, TS is still happy :)
  },
  onDensityChange: setDensity, //using the new onDensityChange option, TS is still happy :)
})
//...
const { density } = table.getState()
return(
  <td
    key={cell.id}
    style={{
      //using our new feature in the code
      padding:
        density === 'sm'
          ? '4px'
          : density === 'md'
            ? '8px'
            : '16px',
      transition: 'padding 0.2s',
    }}
  >
    {flexRender(
      cell.column.columnDef.cell,
      cell.getContext()
    )}
  </td>
)
const table = useReactTable({
  _features: [DensityFeature], //pass our custom feature to the table to be instantiated upon creation
  columns,
  data,
  //...
  state: {
    density, //passing the density state to the table, TS is still happy :)
  },
  onDensityChange: setDensity, //using the new onDensityChange option, TS is still happy :)
})
//...
const { density } = table.getState()
return(
  <td
    key={cell.id}
    style={{
      //using our new feature in the code
      padding:
        density === 'sm'
          ? '4px'
          : density === 'md'
            ? '8px'
            : '16px',
      transition: 'padding 0.2s',
    }}
  >
    {flexRender(
      cell.column.columnDef.cell,
      cell.getContext()
    )}
  </td>
)

Müssen wir das so machen?

Dies ist nur eine neue Möglichkeit, benutzerdefinierten Code parallel zu den integrierten Features in TanStack Table zu integrieren. In unserem obigen Beispiel hätten wir den density-Zustand auch in einem React.useState speichern, unseren eigenen toggleDensity-Handler definieren, wo auch immer, und ihn einfach separat von der Tabelleninstanz in unserem Code verwenden können. Das Erstellen von Tabellen-Features parallel zu TanStack Table anstatt sie tief in die Tabelleninstanz zu integrieren, ist immer noch ein vollkommen gültiger Weg, benutzerdefinierte Features zu erstellen. Je nach Ihrem Anwendungsfall ist dies möglicherweise nicht der sauberste Weg, TanStack Table mit benutzerdefinierten Features zu erweitern.

Unsere Partner
Code Rabbit
AG Grid
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.