Warum sollten Sie Ihr React Data Grid im Jahr 2019 schreiben?

Hallo Habr! Ich bin an der Entwicklung eines ECM-Systems beteiligt. Und in einer kurzen Artikelserie möchte ich unsere Erfahrungen und die Geschichte der Entwicklung meines React Data Grid (im Folgenden einfach ein Grid) teilen, nämlich:


  • warum wir die fertigen Komponenten aufgegeben haben
  • Auf welche Probleme und Aufgaben sind wir bei der Entwicklung unseres Netzes gestoßen?
  • Welchen Gewinn bringt die Entwicklung Ihres Netzes?

Hintergrund


Unser System verfügt über eine Webanwendung, in der Benutzer mit Dokumentenlisten, Suchergebnissen und Verzeichnissen arbeiten. Darüber hinaus können die Listen entweder klein (10 Mitarbeiter) oder sehr groß (50.000 Auftragnehmer) sein. Um diese Listen anzuzeigen, haben wir ein eigenes Raster entwickelt:


Bild


Als wir mit der Entwicklung einer Webanwendung begannen, wollten wir eine vorgefertigte Bibliothek für die Anzeige von Rastern finden, die alles kann, was wir brauchen: Datensätze sortieren und gruppieren, Spalten ziehen und ablegen, mit Mehrfachauswahlen arbeiten, Spaltensummen abschnittsweise filtern und berechnen Laden Sie Daten vom Server herunter und zeigen Sie Zehntausende von Datensätzen an.


Ich werde die letzte Anforderung "Zehntausende von Datensätzen anzeigen" erläutern. In Grids wird diese Anforderung auf verschiedene Arten implementiert: Paging, Infinity-Scrolling, virtuelles Scrollen.


Die Paging- und Infinity-Scrolling-Ansätze sind auf Websites üblich. Sie werden täglich verwendet. Beispiel: Paging in Google:


Bild


Oder Unendlichkeits-Scrollen in demselben Google für Bilder, wobei der nächste Teil der Bilder geladen wird, wenn Sie durch den ersten Teil bis zum Ende scrollen:


Bild


Virtuelles Scrollen (im Folgenden als virtuelles Scrollen bezeichnet) wird im Web jedoch selten verwendet. Der Hauptunterschied zum unendlichen Scrollen besteht in der Möglichkeit, überall schnell durch sehr große Listen zu scrollen. In diesem Fall werden nur die für den Benutzer sichtbaren Daten heruntergeladen und angezeigt.


Bild


Für unsere Webanwendung wollte ich das virtuelle Scrollen verwenden. Ich bin damit einverstanden, dass das Scrollen zu einer beliebigen Stelle in der Liste von 10.000 Einträgen ein ziemlich erfundener Fall ist. Das zufällige Scrollen innerhalb von 500–1000 Datensätzen ist jedoch ein Live-Fall.


Wenn sie das virtuelle Scrollen implementieren, implementieren sie häufig die Software-API zum Verwalten dieses Bildlaufs. Dies ist ein sehr wichtiges Merkmal. Software-Scrolling wird beispielsweise verwendet, um den ausgewählten Datensatz beim Öffnen des Verzeichnisses in der Mitte des Bildschirms zu positionieren:


Bild


Zurück zu den Anforderungen. Was brauchten wir noch:


  • Virtual Scrolling Management API
  • Anpassung des Erscheinungsbilds des Rasters (Zeilen, Spalten, Kontextmenü), damit das Raster in unserer Anwendung nicht fremd aussieht
  • Unterstützung für die von uns verwendeten Technologien: React, Redux und Flexbox
  • Dass das Gitter in ie11 funktioniert hat

Im Allgemeinen gab es viele Anforderungen.


Der erste Versuch (2016). DevExtreme JavaScript-Datenraster


Wir haben uns nicht lange mit vorhandenen Bibliotheken befasst und sind auf das DevExtreme JavaScript Data Grid gestoßen. Aufgrund der funktionalen Anforderungen deckte dieses Raster alle unsere Anforderungen ab und hatte ein sehr ansehnliches Erscheinungsbild. Es war jedoch nicht für technologische Anforderungen geeignet (nicht reagieren, nicht reduzieren, nicht flexboxen). Zu diesem Zeitpunkt hatte DevExtreme kein Reaktionsraster.


Nun, lassen Sie es nicht reagieren, wir haben beschlossen, weil das Gitter schön und funktional ist, werden wir es verwenden. Und sie haben die Bibliothek zu ihrem Projekt hinzugefügt. Es stellte sich heraus, dass wir 3 MB Skripte hinzugefügt haben.


Für ein paar Wochen haben wir das Raster in unsere Webanwendung integriert und die Grundfunktionen erweitert:


  • Wickelte ein Gitter ein, um sich mit React und Redux anzufreunden
  • Erhöhtes virtuelles Scrollen und teilweises Laden von Daten von unserem Webserver
  • Sortierung und Auswahl implementiert

Beim Verschrauben des Gitters wurden zwei schwerwiegende Probleme und eine ganze Reihe weniger schwerwiegender Probleme deutlich.


Erstes ernstes Problem


DevExtreme JavaScript Data Grid mit Redux sehr schwierig zu machen. Wir haben es geschafft, die Spalteneinstellungen zu steuern und Datensätze durch Redux hervorzuheben, aber teilweise geladene Daten in Redux zu speichern und CRUD-Operationen mit ihnen durch Redux durchzuführen - dies ist unrealistisch. Ich musste eine Krücke bauen, die unter Umgehung von Redux die Gitterdaten manipulierte. Die Krücke erwies sich als komplex und zerbrechlich. Dies war die erste Alarmglocke, bei der das Gitter nicht zu uns passte, aber wir schraubten weiter daran.


Zweites ernstes Problem


Es gibt keine virtuelle Bildlaufverwaltungs-API. Wir konnten die Softwaresteuerung des Bildlaufs nicht ablehnen. Wir mussten die DevExtreme-Quellen wiederholen und die interne API für die Bildlaufsteuerung finden. Natürlich hatte diese API eine Reihe von Einschränkungen, da sie für den internen Gebrauch konzipiert wurde. Infolgedessen ist es uns gelungen, die interne API mehr oder weniger für unsere Fälle geeignet zu machen, aber auch hier, um Redux und wieder ein paar Krücken zu umgehen.


Weniger ernsthafte Probleme


Es traten immer weniger schwerwiegende Probleme auf, da die standardmäßige DevExtreme JavaScript Data Grid-Funktionalität für uns nicht vollständig geeignet war und wir versuchten, sie zu korrigieren:


  1. Das Strecken des DevExtreme-Rasters in der Höhe funktioniert nicht. Ich musste einen Hack schreiben, um DevExtreme beizubringen, wie das geht (vielleicht gibt es in den letzten Versionen kein Problem damit).
  2. Wenn der Fokus nicht im Raster liegt, ist es unmöglich, die Auswahl der Linien über die Tastatur zu steuern (und wir brauchten sie). Ich musste meine Tastatursteuerung schreiben.
  3. Beim Ändern der Zusammensetzung der Spalten und der Daten hatten wir das Problem, dass Daten blinken (bei aktiviertem virtuellen Bildlauf).
  4. Das Problem einer großen Anzahl von Anfragen bei der ersten Anzeige des Rasters. Dies machte sich besonders bemerkbar, als wir das Scrollen durch die interne API kontrollierten.
  5. Es ist schwierig, einige Teile des UI-Rasters anzupassen. Beispielsweise bestand über der ausgewählten Gitterlinie der Wunsch, Linienverwaltungsaktionen zu zeichnen (Linie löschen, kopieren, Karte öffnen). Aber wie man dies in DevExtreme einschraubt, war nicht klar, und selbst mit React:
    Bild
  6. Es ist schwierig, die Sortierung zu katalysieren (wir wollten nach Daten sortieren, die nicht im Raster angezeigt und nicht in Spalten abgebildet werden).
  7. Krücken sind erforderlich, um die Reaktionskomponente in die Gitterzellen einzuschrauben (schließlich befindet sich das Gitter nicht in der Reaktion).
  8. Keine Eingabe von DevExtreme-Code (Flow / Typoskript).
  9. Geschwindigkeitsproblem beim langen virtuellen Scrollen.
  10. Geschwindigkeitsproblem beim Strecken / Neuanordnen von Spalten (nach längerem virtuellem Scrollen).
  11. Die Größe der Grid-Skripte beträgt 3 MB.

Das DevExtreme-Funktionsraster enthielt zwar alles, was wir brauchten, aber ich wollte fast alle Standardfunktionen neu schreiben. Während seiner Verwendung wurden Hunderte von Codezeilen hinzugefügt, die schwer zu verstehen waren und versuchten, die Probleme der Interaktion mit Redux zu lösen und zu reagieren. Es war schwierig, ein nicht reagierendes Gitter in einer Reaktionsanwendung zu verwenden.


Ablehnung von DevExtreme. Suche nach Alternativen


Nach einiger Zeit mit DevExtreme wurde beschlossen, es aufzugeben. Werfen Sie alle Hacks, komplexen Code sowie 3 MB DevExtreme-Skripte weg. Und finde oder schreibe ein neues Gitter.


Dieses Mal konzentrieren wir uns mehr auf das Studium bestehender Netze. MS Fabric DetailsList, ReactVirtualized Grid, DevExtreme React Grid, Telerik Grid und KendoUI Grid wurden untersucht.
Die Anforderungen blieben gleich, aber sie nahmen bereits in einer Liste Gestalt an, die wir verstanden haben.


Technologische Anforderungen:


  • reagieren
  • Redux
  • Flexbox

Funktionsanforderungen:


  • Virtuelles Scrollen (mit der Fähigkeit, Zehntausende von Datensätzen anzuzeigen)
  • Scrolling Management API
  • Speichern von Daten und Rastereinstellungen in Redux
  • Laden von Daten von einem Webserver
  • Spaltenverwaltung (Dehnung / Neuanordnung / Sichtbarkeitskontrolle)
  • Spaltensortierung + Filterung
  • Mehrfachauswahl
  • Like-Suche mit Hintergrundbeleuchtung
  • Horizontales Scrollen
  • Tastatur
  • Kontextmenü (in einer Zeile, in einem leeren Bereich, in Spalten)
  • Unterstützung dh11, Rand, Chrom, ff, Safari

Zu diesem Zeitpunkt war die erste Version von DevExtreme React Grid bereits erschienen, wir haben sie jedoch aus folgenden Gründen sofort gelöscht:


  • Virtuelles Scrollen wird in ie11 nicht unterstützt
  • Das virtuelle Scrollen funktioniert nicht in Verbindung mit dem Batch-Herunterladen von Daten vom Server (obwohl es einige Problemumgehungen zu geben scheint).
  • Und vor allem wollte ich nicht auf denselben Rechen treten, wenn ich die Hälfte der Standardfunktionalität eines Drittanbieter-Grids neu schreiben wollte.

Die Analyse bestehender Lösungen ergab, dass es keine „Silberkugel“ gibt. Ein Raster, das alle unsere Anforderungen abdeckt, existiert nicht. Es wurde beschlossen, ein eigenes Raster zu schreiben, das wir in Bezug auf die Funktionalität in die von uns benötigte Richtung entwickeln und mit den von unserem Produkt benötigten Technologien befreundet sein werden.


Entwickeln Ihres React Data Grid


Die Entwicklung des Rasters begann mit Prototypen, bei denen die schwierigsten Themen für uns getestet wurden:


  • virtuelles Scrollen
  • Speicherung aller Grid-Daten in Redux

Virtuelles Scrollen


Am schwierigsten stellte sich das virtuelle Scrollen heraus. Zum größten Teil wird es auf drei Arten hergestellt:


1. Seitenvirtualisierung
Daten werden in Teilen - Seiten gezeichnet. Beim Scrollen werden sichtbare Seiten hinzugefügt, unsichtbare gelöscht. Die Seite besteht aus 20-60 Zeilen (normalerweise ist die Größe anpassbar). Hier gingen die Produkte hin: DevExtreme JavaScript Data Grid, MS Fabric DetailsList.


Bild


2. Zeile für Zeile Virtualisierung
Es werden nur sichtbare Linien gezeichnet. Sobald eine Zeile den Bildschirm verlässt, wird sie sofort gelöscht. Die Produkte gingen diesen Weg: ReactVirtualized Grid, DevExtreme React Grid, Telerik Grid.


Bild


3. Leinwand
Alle Linien und deren Inhalt werden mit Canvas gezeichnet. Dies hat Google Text & Tabellen getan.


Bild


Bei der Entwicklung des Rasters haben wir Prototypen für alle drei Virtualisierungsoptionen (auch für Canvas) erstellt. Und sie entschieden sich für eine seitenweise Virtualisierung.


Warum andere Optionen aufgeben?


Die zeilenweise Virtualisierung hatte Probleme mit der Rendergeschwindigkeit im Prototyp. Sobald der Inhalt der Zeilen komplizierter wurde (viel Text, Hervorheben, Zuschneiden, Symbole, eine große Anzahl von Spalten und überall Flexbox), wurde es teuer, Zeilen mehrmals pro Sekunde hinzuzufügen / zu entfernen. Natürlich hängen die Ergebnisse auch vom Browser ab (wir haben Unterstützung geleistet, einschließlich für ie11, edge):


Bild


Die Canvas-Option war sehr verführerisch in der Rendergeschwindigkeit, aber mühsam. Es wurde vorgeschlagen, alles zu zeichnen: Text, Textumbruch, Texttrimmen, Hervorheben, Symbole, Trennlinien, Hervorheben, Einrücken. Reagieren Sie auf das Klicken mit der Maus auf der Leinwand und markieren Sie die Linien, wenn Sie den Mauszeiger darüber halten. Gleichzeitig sollten einige Dom-Elemente (mit Hinweisen, „Popup-Aktionen“ in der Zeile) über Canvas angewendet werden. Es war immer noch notwendig, das Problem des Verwischens von Text und Symbolen in Canvas zu lösen. All dies ist lang und schwierig zu tun. Obwohl wir den Prototyp gemeistert haben. Gleichzeitig würde jede Anpassung von Zeilen und Zellen in der Zukunft zu einer großen Mühsal für uns führen.


Die Vorteile von Paging


Die ausgewählte seitenweise Virtualisierung hatte Vorteile gegenüber der zeilenweisen Virtualisierung, die ihre Wahl bestimmte:


  • Wenn die Seite bereits gerendert ist, ist das Scrollen innerhalb der Seite billig (der DOM-Baum ändert sich beim Scrollen nicht). Die zeilenweise Virtualisierung für kleinere Bildläufe erfordert das Ändern des DOM-Baums. Dies ist teuer, wenn der DOM-Baum komplex ist und überall Flexbox verwendet wird.
  • Bei kleinen Listen (<200 Einträge) können Seiten nicht gelöscht werden. Fügen Sie einfach hinzu. Früher oder später werden alle Seiten erstellt und das Scrollen ist völlig kostenlos (in Bezug auf die Renderzeit).

Auswahl der Seitengröße


Ein separates Problem ist die Wahl der Seitengröße. Ich habe oben geschrieben, dass die Größe anpassbar ist und normalerweise 20-60 Zeilen beträgt. Eine große Seite wird lange gezeichnet, eine kleine Seite führt beim Scrollen zu einer häufigen Anzeige eines "weißen Bildschirms". Experimentell wurde eine Seitengröße von 25 Zeilen ausgewählt. Für dh11 wurde die Größe jedoch auf 5 Zeilen reduziert. Es scheint, dass die Benutzeroberfläche im IE schneller reagiert, wenn Sie viele kleine Seiten mit kleinen Verzögerungen zeichnen als eine große mit großer Verzögerung.


Reagieren und virtuelles Scrollen


Die Seitenvirtualisierung musste mithilfe von React implementiert werden. Dazu sollten mehrere Aufgaben gelöst werden:


Aufgabe 1. Wie füge ich Seiten hinzu, indem ich beim Scrollen reagiere?


Um dieses Problem zu lösen, wurden folgende Konzepte eingeführt:


  • Seitenmodell
  • Seitenaufruf

Ein Modell ist eine Information, auf der eine Ansicht erstellt werden soll. Eine Ansicht ist eine Reaktionskomponente.


Bild


Tatsächlich bestand die Aufgabe der Virtualisierung danach darin, Seitenmodelle zu bearbeiten: Speichern Sie eine Liste von Seitenmodellen, fügen Sie beim Scrollen Modelle hinzu und entfernen Sie sie. Und schon aus der Liste der Modelle durch React Build / Rebuild das Display:


Bild


Im Zuge der Implementierung wurden die Regeln für die Arbeit mit Seitenmodellen festgelegt:


  • Seiten sollten einzeln hinzugefügt werden. Geben Sie nach jeder Zugabe Zeit zum Zeichnen. Es ist akzeptabel, alle 300-500 ms 1 Seite hinzuzufügen - dies ist eine schnelle Bildlaufsituation. Wenn Sie beispielsweise 5 Seiten gleichzeitig hinzufügen, hängt die Benutzeroberfläche von ihrer Konstruktion ab.
  • Seiten müssen nicht in Dutzenden gehalten werden. Ein Beispiel für eine Problemsituation: 20 Seiten werden angezeigt, der Benutzer wechselt zu einer anderen Liste und alle 20 Seiten müssen gleichzeitig gelöscht werden. Das Entfernen einer großen Anzahl von Seiten ist ein teurer Vorgang. Das Bereinigen des DOM-Baums dauert 1 Sekunde. Um dies zu vermeiden, ist es besser, nicht mehr als 10 Seiten gleichzeitig zu behalten.
  • Bei jeder Spaltenmanipulation (Neuanordnung, Hinzufügen, Löschen, Strecken) ist es besser, Seiten zu löschen, die für den Benutzer nicht im Voraus sichtbar sind. Dadurch wird eine teure Neuerstellung aller gerenderten Seiten vermieden.

Aufgabe 2. Wie wird die Bildlaufleiste angezeigt?


Beim virtuellen Scrollen wird davon ausgegangen, dass eine Bildlaufleiste verfügbar ist, die die Größe der Liste berücksichtigt und es Ihnen ermöglicht, zu einem beliebigen Ort zu scrollen:


Bild


Wie kann man eine solche Bildlaufleiste anzeigen? Die einfachste Lösung besteht darin, anstelle von realen Daten ein unsichtbares Div der erforderlichen Größe zu zeichnen. Und schon oben auf diesem Div zeigen wir die sichtbaren Seiten an:


Bild


Aufgabe 3. Wie kann die Größe des Ansichtsfensters überwacht werden?


Das Ansichtsfenster ist der sichtbare Datenbereich des Rasters. Warum ihre Größe im Auge behalten? Berechnung der Anzahl der Seiten, die dem Benutzer angezeigt werden müssen. Angenommen, wir haben eine kleine Seitengröße (5 Zeilen) und eine große Bildschirmauflösung (1920 x 1080). Wie viele Seiten muss der Benutzer anzeigen, um das gesamte Ansichtsfenster zu schließen?


Bild


Sie können dieses Problem lösen, wenn Sie die Höhe des Ansichtsfensters und die Höhe einer Seite kennen. Lassen Sie uns nun die Aufgabe komplizieren. Nehmen wir an, der Benutzer ändert die Skalierung im Browser stark - setzt 50%:


Bild


Die Situation mit der Skala zeigt, dass es nicht ausreicht, die Größe des Ansichtsfensters einmal zu ermitteln. Sie müssen die Größe überwachen. Und jetzt erschweren wir die Aufgabe vollständig: HTML-Elemente haben kein Größenänderungsereignis, das Sie abonnieren und die Größe überwachen können. Nur das Fensterobjekt hat die Größe geändert.


Das erste, was mir in den Sinn kommt, ist die Verwendung eines Timers und die ständige Abfrage der Höhe des HTML-Elements. Es gibt jedoch eine noch bessere Lösung, die wir im DevExtreme JavaScript-Datenraster gesehen haben: Erstellen Sie einen unsichtbaren Iframe, dehnen Sie ihn auf die Rastergröße aus und abonnieren Sie das Größenänderungsereignis von iframe.contentWindow:


Bild


Bild


Zusammenfassung


PS Dies ist nicht das Ende. Im nächsten Artikel werde ich erzählen, wie wir uns mit Redux angefreundet haben.


Um ein vollständiges virtuelles Scrollen zu erhalten, mussten viele andere Aufgaben gelöst werden. Aber die oben beschriebenen waren die interessantesten. Hier sind einige andere Aufgaben, die ebenfalls angezeigt werden:


  • Berücksichtigen Sie beim Hinzufügen / Entfernen von Seiten die Richtung und Geschwindigkeit des Bildlaufs.
  • Berücksichtigen Sie Datenänderungen, um die Neuerstellung von Seitenmodellen zu minimieren. Wenn Sie beispielsweise eine Zeile gelöscht oder eine Zeile hinzugefügt haben, was tun Sie mit bereits gerenderten Seiten? Alles wegwerfen oder etwas zurücklassen? Es gibt Raum für Optimierungen.
  • Ordnen Sie beim Ändern der Auswahl die minimal erforderliche Anzahl von Seiten neu an.

Wenn Sie Fragen zur Implementierung haben, können Sie diese in die Kommentare schreiben.

Source: https://habr.com/ru/post/de457504/


All Articles