Kampf um die Leistung wirklich großer Reaktionsformen

Bei einem der Projekte stießen wir auf Formulare aus mehreren Dutzend Blöcken, die voneinander abhängen. Wie üblich können wir aufgrund der NDA nicht im Detail über die Aufgabe sprechen, aber wir werden versuchen, unsere Erfahrung mit der „Zähmung“ der Leistung dieser Formulare anhand eines abstrakten (sogar leicht nicht lebenden) Beispiels zu beschreiben. Ich werde Ihnen sagen, welche Schlussfolgerungen wir aus einem React-Projekt mit Final-Form gezogen haben.



Bild


Stellen Sie sich vor, Sie können mit dem Formular einen ausländischen Pass für eine neue Probe erhalten, während Sie den Erhalt eines Schengen-Visums über einen Vermittler - ein Visa-Zentrum - bearbeiten. Dieses Beispiel scheint bürokratisch genug zu sein, um unsere Komplexität zu demonstrieren.



Bei unserem Projekt sehen wir uns also mit einer Form von vielen Blöcken mit bestimmten Eigenschaften konfrontiert:



  • Unter den Feldern befinden sich Eingabefelder, Mehrfachauswahl- und Autocomplete-Felder.
  • Die Blöcke sind miteinander verbunden. Angenommen, Sie müssen in einem Block die Daten des internen Passes angeben, und direkt darunter befindet sich ein Block mit den Daten des Visumantragstellers. Gleichzeitig wird eine Vereinbarung mit einem Visa-Zentrum für einen internen Pass ausgestellt.

  • – , , ( 10 , ) .
  • , , . , 10- , . : .
  • . . .


Die endgültige Form nahm vertikal ungefähr 6.000 Pixel ein - das sind ungefähr 3-4 Bildschirme, insgesamt mehr als 80 verschiedene Felder. Im Vergleich zu diesem Formular scheinen die Anträge bei den staatlichen Diensten nicht so groß zu sein. Das Nächste in Bezug auf die Fülle an Fragen ist wahrscheinlich ein Fragebogen des Sicherheitsdienstes an ein großes Unternehmen oder eine langweilige Meinungsumfrage über die Präferenzen von Videoinhalten.



Bei echten Problemen sind große Formen nicht so häufig. Wenn wir versuchen, ein solches Formular „frontal“ zu implementieren - analog zu unserer gewohnten Arbeit mit kleinen Formularen -, ist das Ergebnis unmöglich zu verwenden.



Das Hauptproblem besteht darin, dass bei Eingabe jedes Buchstabens in die entsprechenden Felder das gesamte Formular neu gezeichnet wird, was insbesondere auf Mobilgeräten zu Leistungsproblemen führt.



Und es ist schwierig, mit dem Formular nicht nur für Endbenutzer, sondern auch für Entwickler, die es pflegen müssen, umzugehen. Wenn Sie keine besonderen Schritte unternehmen, ist die Beziehung der Felder im Code schwer zu verfolgen. Änderungen an einer Stelle haben Konsequenzen, die manchmal schwer vorherzusagen sind.



Wie wir Final-Form bereitgestellt haben



Das Projekt verwendete React und TypeScript (als wir unsere Aufgaben erledigten, wechselten wir vollständig zu TypeScript). Um die Formulare zu implementieren, haben wir daher die React Final-Formularbibliothek von den Erstellern von Redux Form übernommen.



Zu Beginn des Projekts haben wir das Formular in separate Blöcke aufgeteilt und die in der Dokumentation für das endgültige Formular beschriebenen Ansätze verwendet. Leider führte dies dazu, dass die Eingabe in einem der Felder eine Änderung in der gesamten großen Form bewirkte. Da die Bibliothek relativ neu ist, ist die Dokumentation dort noch jung. Es werden nicht die besten Rezepte zur Verbesserung der Leistung großer Formen beschrieben. So wie ich es verstehe, sind nur sehr wenige Menschen bei Projekten damit konfrontiert. Bei kleinen Formularen wirken sich einige zusätzliche Neuzeichnungen der Komponente nicht auf die Leistung aus.



Abhängigkeiten



Die erste Unklarheit, der wir uns stellen mussten, war, wie genau die Abhängigkeit zwischen den Feldern implementiert werden sollte. Wenn Sie streng nach der Dokumentation arbeiten, verlangsamt sich das überwachsene Formular aufgrund der großen Anzahl miteinander verbundener Felder. Der Punkt sind Abhängigkeiten. In der Dokumentation wird vorgeschlagen, ein Abonnement für ein externes Feld neben dem Feld zu erstellen. So war es in unserem Projekt - angepasste Versionen von React-Final-Form-Listenern, die für die Verbindung der Felder verantwortlich waren, lagen an derselben Stelle wie die Komponenten, dh sie lagen in jeder Ecke. Abhängigkeiten waren schwer zu finden. Dies hat die Menge an Code aufgebläht - die Komponenten waren gigantisch. Und alles funktionierte langsam. Und um etwas in der Form zu ändern, mussten Sie viel Zeit mit der Suche in allen Projektdateien verbringen (das Projekt enthält ungefähr 600 Dateien, von denen mehr als 100 Komponenten sind).



Wir haben mehrere Versuche unternommen, die Situation zu verbessern.



Wir mussten unseren eigenen Selektor implementieren, der nur die Daten auswählt, die von einem bestimmten Block benötigt werden.



<Form onSubmit={this.handleSubmit} initialValues={initialValues}>
   {({values, error, ...other}) => (
      <>
      <Block1 data={selectDataForBlock1(values)}/>
      <Block2 data={selectDataForBlock2(values)}/>
      ...
      <BlockN data={selectDataForBlockN(values)}/>
      </>
   )}
</Form>


Wie Sie sich vorstellen können, musste ich mir meine eigenen einfallen lassen memoize pick([field1, field2,...fieldn]).



All dies in Verbindung mit PureComponent (React.memo, reselect)führte dazu, dass die Blöcke nur dann neu gezeichnet werden, wenn sich die Daten ändern, von denen sie abhängen (ja, wir haben die Reselect-Bibliothek in das Projekt eingeführt, das zuvor nicht verwendet wurde, mit dessen Hilfe wir fast alle Datenanforderungen ausführen).



Aus diesem Grund haben wir zu einem Listener gewechselt, der alle Abhängigkeiten für das Formular beschreibt. Wir haben die Idee dieses Ansatzes aus dem Projekt zur Berechnung der endgültigen Form ( https://github.com/final-form/final-form-calculate ) übernommen und ihn unseren Anforderungen hinzugefügt.



<Form
   onSubmit={this.handleSubmit}
   initialValues={initialValues}
   decorators={[withContextListenerDecorator]}
>

   export const listenerDecorator = (context: IContext) =>
   createDecorator(
      ...block1FieldListeners(context),
      ...block2FieldListeners(context),
      ...
   );

   export const block1FieldListeners = (context: any): IListener[] => [
      {
      field: 'block1Field',
      updates: (value: string, name: string) => {
         //    block1Field       ...
         return {
            block2Field1: block2Field1NewValue,
            block2Field2: block2Field2NewValue,
         };
      },
   },
];


Als Ergebnis haben wir die erforderliche Beziehung zwischen den Feldern erhalten. Außerdem werden die Daten an einem Ort gespeichert und transparenter verwendet. Außerdem wissen wir, in welcher Reihenfolge die Abonnements ausgelöst werden, da dies ebenfalls wichtig ist.



Validierung



In Analogie zu Abhängigkeiten haben wir uns mit der Validierung befasst.



In fast allen Bereichen mussten wir überprüfen, ob die Person das richtige Alter eingegeben hat (z. B. ob der Dokumentensatz dem angegebenen Alter entspricht). Von Dutzenden verschiedener Validatoren, die über alle Formen verteilt sind, haben wir zu einem globalen gewechselt und ihn in separate Blöcke unterteilt:



  • Validator für Passdaten,
  • Validator für Reisedaten,
  • für Daten zu zuvor ausgestellten Visa,
  • usw.


Dies hatte fast keinen Einfluss auf die Leistung, beschleunigte jedoch die weitere Entwicklung. Wenn Sie jetzt Änderungen vornehmen, müssen Sie nicht die gesamte Datei durchgehen, um zu verstehen, was in einzelnen Validatoren geschieht.



Wiederverwendung von Code



Wir begannen mit einer großen Form, auf der wir unsere Ideen rollten, aber im Laufe der Zeit wuchs das Projekt - eine andere Form erschien. Natürlich haben wir in der zweiten Form dieselben Ideen verwendet und sogar den Code wiederverwendet.



Bisher haben wir die gesamte Logik bereits in separate Module verschoben. Warum also nicht mit dem neuen Formular verbinden? Auf diese Weise haben wir den Code und die Entwicklungsgeschwindigkeit erheblich reduziert.



In ähnlicher Weise hat das neue Formular jetzt Typen, Konstanten und Komponenten, die mit dem alten identisch sind - zum Beispiel haben sie eine allgemeine Berechtigung.



Anstelle von Summen



Die Frage ist logisch: Warum haben wir keine andere Bibliothek für Formulare verwendet, da diese Schwierigkeiten hatte? Aber große Formen werden trotzdem ihre eigenen Probleme haben. In der Vergangenheit habe ich selbst mit Formik gearbeitet. In Anbetracht der Tatsache, dass wir Lösungen für unsere Fragen gefunden haben, erwies sich die endgültige Form als bequemer.



Insgesamt ist dies ein großartiges Werkzeug für die Arbeit mit Formularen. Und zusammen mit einigen Regeln für die Entwicklung der Codebasis hat er uns geholfen, die Entwicklung erheblich zu optimieren. Der zusätzliche Bonus all dieser Arbeit ist die Fähigkeit, neue Teammitglieder schneller auf den neuesten Stand zu bringen.



Nach dem Hervorheben der Logik wurde viel klarer, wovon ein bestimmtes Feld abhängt - es ist nicht erforderlich, drei Anforderungsblätter in der Analyse zu lesen. Unter diesen Bedingungen dauert die Prüfung von Fehlern jetzt mindestens zwei Stunden, obwohl es einige Tage dauern kann, bis all diese Verbesserungen vorliegen. Während dieser ganzen Zeit suchte der Entwickler nach einem Phantomfehler, der nicht klar ist, was er sich manifestiert.



Autoren des Artikels: Oleg Troshagin, Maxilekt.



PS Wir veröffentlichen unsere Artikel auf mehreren Websites der Runet. Abonnieren Sie unseren Seiten VK , FB , Instagram oder Telegramm Kanal über alle unsere Publikationen und andere Nachrichten von Maxilect zu lernen.



All Articles