enhancement
major
minor
major
minor
major
#29920
TL Views: Die Zeilenauswahl in einer React-Tabelle braucht bis zu einer Sekunde, weil jede Auswahl alle Zellen neu überträgt und bei einer Tabelle mit Eingabekanal alle Zellen neu erzeugt
Problem
In einer React-Tabelle (<table> der View-Schicht, TableViewControl) dauert es nach einem Klick auf eine Zeile spürbar lange, bis die Zeile als ausgewählt angezeigt wird. In der Demo-Tabelle tl-demo-react → Daten → Attribute → Tabelle (30 sichtbare Spalten) wurden gemessen:
| = Zeilen = | = Klick bis Anzeige der Auswahl = | = Serverzeit des Kommandos = | = übertragene Daten pro Klick = |
| 18 | 370–650 ms | ~490 ms | 513 KB |
| 204 | ~1140 ms | ~1090 ms | 2 MB |
Die gesamte Zeit entsteht auf dem Server (der Client rendert innerhalb von 10 ms nach dem letzten Update). Nach einem Profil der Anfrage entfallen 58 % auf das Erzeugen von Zellen-Controls, 30 % auf die JSON-Serialisierung des Zeilenfensters, 11 % auf das Neu-Sortieren/-Filtern der Zeilenliste und 1 % auf die Zeilenabfrage selbst.
Ursachen
Zwei voneinander unabhängige Fehler:
- TableViewControl behandelt eine Auswahländerung wie eine Änderung der Zeilen. Das Merkmal „ausgewählt“ ist Teil des Zustands jeder Zeile. Jede Auswahl (Klick, Strg-/Shift-Klick, Tastatur, „alle auswählen“, Auswahl per Kanal) überträgt deshalb das gesamte gepufferte Zeilenfenster neu, jede Zeile mit dem vollständigen Zustand aller ihrer Zellen-Controls (bei 204 Zeilen ca. 1 MB pro Übertragung).
- Die View-Schicht behandelt die Änderung des eigenen Auswahlkanals wie eine Datenänderung. Eine Tabelle, die ihren Auswahlkanal (selection="…") zusätzlich unter <inputs> führt (z. B. weil ein Filter-Preset von der Auswahl abhängt), wertet bei jeder Auswahl über RowSourceObserver die Zeilenabfrage neu aus, ersetzt die Zeilenliste (setElements, Neu-Sortieren/-Filtern) und baut mit refreshData() alle gepufferten Zellen-Controls ab und neu auf – obwohl die Zeilen einer Tabelle von ihrer eigenen Auswahl nicht abhängen können. Anschließend überträgt die Auswahlbehandlung das Zeilenfenster ein zweites Mal.
Lösung
- TableViewControl (tl-layout-react): Die Auswahl ist ein eigener Zustandsschlüssel selectedRows mit den Indizes der ausgewählten Zeilen des gepufferten Fensters. Eine Auswahländerung (Klick, Strg-/Shift-Klick, Tastatur, „alle auswählen“, programmatische Auswahl einer sichtbaren Zeile) überträgt nur noch diesen Schlüssel zusammen mit Cursor und Zählern; das Zeilenfenster rows wird nur übertragen, wenn sich die Zeilen tatsächlich ändern (Scrollen, Sortieren, Filtern, Datenaktualisierung, invalidierte Zellen der editierbaren Tabelle, Auswahl einer Zeile außerhalb des Fensters). Der Client (TLTableView) leitet den Auswahlzustand einer Zeile aus diesem Schlüssel ab.
- View-Schicht (tl-layout-view): RowSourceObserver und ObservableTreeModel unterscheiden, was die Änderung eines Eingabekanals auslöst. Das Tabellenelement leitet die Rollen seiner Eingaben aus seiner Konfiguration ab (AbstractTableElement.inputRoles):
- Kanäle, die eine Spaltendeklaration selbst liest (inputs einer <computed-column>, <dynamic-columns>, <embedded-columns> oder eines Spaltenfilters wie ScriptedFilter, siehe ColumnDeclaration.inputs()), führen wie bisher zur vollständigen Aktualisierung: Zeilen neu abfragen, Zellen neu bauen.
- Der eigene Auswahlkanal der Tabelle unter <inputs> löst weder eine Abfrage noch eine Datenaktualisierung aus, sondern nur die leichte Aktualisierung: Die Presets werden neu berechnet, und nur wenn eine row-class konfiguriert ist, wird das Zeilenfenster ohne Neuaufbau der Zellen erneut übertragen (TableViewControl.repaintRows()). Beim <tree-table> wird der Baum nicht neu gebaut.
- Jeder andere Eingabekanal fragt die Zeilen wie bisher neu ab; gleicht das Ergebnis den angezeigten Zeilen, bleiben die Zellen erhalten und nur die leichte Aktualisierung läuft.
- Eine Änderung eines beobachteten Modellobjekts führt weiterhin zur vollständigen Aktualisierung, da sich Zelleninhalte geändert haben können.
Ergebnis
Demo-Tabelle mit 204 Zeilen und 30 Spalten nach der Änderung: Klick bis Anzeige der Auswahl ca. 70 ms (im Browser gemessen), ca. 400 Byte übertragene Daten pro Klick statt 2 MB.
Verifikation: Unit-Tests in tl-layout-react (TestTableSelectionState: eine Auswahl überträgt keine Zeilen bei Einzel-, Strg-, Shift-Auswahl, „alle auswählen“ und nach einem Veto; Scrollen und die Auswahl einer entfernten Zeile übertragen die Zeilen) und tl-layout-view (TestRowSourceObserver, TestObservableTreeModel, TestTableSelectionInput, TestColumnDeclarations: Änderung des Auswahlkanals ohne Abfrage und mit erhaltenen Zellen; Änderung eines anderen Kanals mit gleichen Zeilen ohne Neuaufbau, mit anderen Zeilen und bei Modelländerung mit Aktualisierung; eine Spalte, die den Auswahlkanal liest, macht ihn zur gewöhnlichen Eingabe) sowie die Messung in der Demo-Tabelle.