Heim > Web-Frontend > js-Tutorial > Existenzielle Reaktionsfragen und ein perfekter modaler Dialog

Existenzielle Reaktionsfragen und ein perfekter modaler Dialog

Patricia Arquette
Freigeben: 2025-01-03 03:44:41
Original
673 Leute haben es durchsucht

Existential React questions and a perfect Modal Dialog

Was ist Ihrer Meinung nach das Komplizierteste an React? Neu rendern? Kontext? Portale? Parallelität?

Nein.

Der schwierigste Teil von React ist alles, was nicht-React drumherum ist. Die Antwort auf die Frage „Wie funktionieren die oben aufgeführten Dinge?“ ist unkompliziert: Man muss lediglich dem Algorithmus folgen und sich Notizen machen. Das Ergebnis wird definitiv und immer dasselbe sein (wenn Sie es richtig nachverfolgen). Es sind nur Wissenschaft und Fakten.

Aber was ist mit „Was macht eine Komponente gut?“ oder „Wie setzt man … (etwas) richtig um?“ oder sogar „Soll ich eine Bibliothek verwenden oder meine eigene Lösung erstellen?“ Die einzig sachlich richtige Antwort lautet hier: „Es kommt darauf an.“ Es ist zufällig das am wenigsten hilfreiche.

Ich wollte für den neuen Artikel etwas Besseres finden. Aber da es für diese Art von Fragen keine einfachen Antworten und universellen Lösungen geben kann, entpuppte sich der Artikel eher als eine Art Durchlauf meines Denkprozesses und nicht als „Das ist die Antwort, tu es immer.“ Ich hoffe, es ist immer noch nützlich.

Was ist also nötig, um eine Funktion von einer Idee in eine produktionsreife Lösung umzuwandeln? Versuchen wir, einen einfachen modalen Dialog zu implementieren und zu sehen. Was kann daran möglicherweise kompliziert sein? ?

Schritt 1: Beginnen Sie mit der einfachsten Lösung

Beginnen wir mit dem, was manchmal als „Spike“ bezeichnet wird – der einfachsten möglichen Implementierung, die dabei helfen kann, potenzielle Lösungen zu erkunden und weitere Anforderungen zu erfassen. Ich weiß, dass ich einen modalen Dialog umsetze. Nehmen wir an, ich habe ein hübsches Design wie dieses:

Existential React questions and a perfect Modal Dialog

Ein Dialog ist im Grunde ein Element auf dem Bildschirm, das erscheint, wenn auf etwas wie eine Schaltfläche geklickt wird. Genau da fange ich also an.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Status, eine Schaltfläche, die auf Klicks wartet, und ein zukünftiger Dialog, der angezeigt wird, wenn der Status wahr ist. Dialog soll auch eine „Schließen“-Aktion haben:

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es hat auch einen „Hintergrund“ – ein anklickbares halbtransparentes Div, das den Inhalt überlagert und beim Klicken das Verschwinden des Modals auslöst.

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Alle zusammen:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Normalerweise füge ich auch schon früh dezente Styles hinzu. Wenn ich sehe, dass die von mir implementierte Funktion auf dem Bildschirm genauso aussieht, wie sie sein soll, hilft mir das beim Nachdenken. Außerdem kann es das Layout der Funktion beeinflussen, und genau das geschieht mit diesem Dialog.

Fügen wir schnell CSS für den Hintergrund hinzu – es ist nichts Besonderes, nur ein halbtransparenter Hintergrund auf einem Div mit Position: Fixed, das den gesamten Bildschirm einnimmt:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der Dialog ist etwas interessanter, da er in der Mitte des Bildschirms positioniert werden muss. Es gibt natürlich 1001 Möglichkeiten, dies in CSS zu erreichen, aber meine liebste und wahrscheinlich einfachste ist diese:

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir verwenden eine „feste“ Position, um den Layoutbeschränkungen zu entgehen, fügen 50 % nach links und oben hinzu, um das Div in die Mitte zu verschieben, und transformieren es um 50 % zurück. Der linke und der obere Teil werden relativ zum Bildschirm berechnet und die Transformation erfolgt relativ zur Breite/Höhe des Div selbst, sodass es unabhängig von seiner Breite oder der Breite des Bildschirms genau in der Mitte erscheint.

Der letzte Teil des CSS in diesem Schritt besteht darin, den Dialog selbst und die Schaltfläche „Schließen“ richtig zu formatieren. Ich werde es hier nicht kopieren und einfügen, da die tatsächlichen Stile nicht so wichtig sind. Schauen Sie sich einfach das Beispiel an:

Schritt 2: Halten Sie inne, stellen Sie Fragen und denken Sie nach

Da ich nun eine grobe Implementierung der Funktion habe, ist es an der Zeit, sie „real“ zu machen. Dazu müssen wir im Detail verstehen, was genau wir hier lösen wollen und für wen. Technisch gesehen sollten wir uns darüber im Klaren sein, dass bevor etwas codiert wird, was häufig der Fall ist, dass dieser Schritt Schritt 1 sein sollte.

Ist dieser Dialog Teil eines Prototyps, der so schnell wie möglich implementiert, den Investoren einmal gezeigt und nie wieder verwendet werden muss? Oder ist es vielleicht Teil einer generischen Bibliothek, die Sie auf npm und Open Source veröffentlichen möchten? Oder ist es vielleicht Teil der Designsysteme, die Ihr Unternehmen mit 5.000 Mitarbeitern verwenden wird? Oder ist es Teil der internen Ausstattung Ihres kleinen 3-Personen-Teams und sonst nichts? Oder arbeiten Sie vielleicht für etwas wie TikTok und dieser Dialog wird Teil der Web-App sein, die nur auf Mobilgeräten verfügbar ist? Oder arbeiten Sie vielleicht für eine Agentur, die Apps nur für die Regierung schreibt?

Die Beantwortung dieser Fragen gibt die Richtung vor, was als nächstes beim Codieren zu tun ist.

Wenn es sich nur um einen Prototyp zur einmaligen Verwendung handelt, ist er möglicherweise bereits gut genug.

Wenn es als Teil einer Bibliothek als Open-Source-Lösung bereitgestellt werden soll, muss es über eine sehr gute Allzweck-API verfügen, die jeder Entwickler auf der Welt verwenden und verstehen kann, über zahlreiche Tests und eine gute Dokumentation.

Der Dialog, der Teil der Designsysteme einer Organisation mit 5.000 Personen ist, muss den Designrichtlinien der Organisation entsprechen und kann hinsichtlich der in das Repo eingebrachten externen Abhängigkeiten eingeschränkt sein. Daher müssen Sie möglicherweise viele Dinge von Grund auf implementieren, anstatt npm install new-fancy-tool auszuführen.

Der Dialog einer Behörde, die für die Regierung arbeitet, muss wahrscheinlich der zugänglichste und gesetzeskonformste Dialog im Universum sein. Andernfalls könnte die Agentur die Regierungsaufträge verlieren und bankrott gehen.

Und so weiter und so weiter.

Für den Zweck dieses Artikels gehen wir davon aus, dass der Dialog Teil einer neuen, derzeit laufenden Neugestaltung einer bestehenden großen kommerziellen Website mit täglich Tausenden von Benutzern aus der ganzen Welt ist. Die Neugestaltung ist so weit fortgeschritten, dass der einzige Entwurf mit dem Dialog, den ich habe, dieser ist:

Existential React questions and a perfect Modal Dialog

Der Rest kommt später, die Designer sind überfordert. Außerdem bin ich Teil des festen Teams, das die Neugestaltung durchführt und die Website in Zukunft pflegt, und kein externer Auftragnehmer, der für ein einzelnes Projekt beauftragt wird.

In diesem Fall verschafft mir allein dieses Bild und das Wissen um das Ziel unseres Unternehmens genügend Informationen, um vernünftige Annahmen zu treffen und 90 % des Dialogs umzusetzen. Der Rest der 10 % kann später noch feinabgestimmt werden.

Das sind die Annahmen, die ich auf Grundlage der oben genannten Informationen treffen kann:

  • Die bestehende Website hat täglich Tausende von Benutzern aus der ganzen Welt, daher muss ich sicherstellen, dass der Dialog zumindest sowohl auf großen und mobilen Bildschirmen als auch auf verschiedenen Browsern funktioniert. Im Idealfall muss ich vorhandene Analysen überprüfen, um absolut sicher zu sein, aber das ist eine ziemlich sichere Sache.

  • Mehr als ein Entwickler schreibt dafür Code, und der Code wird bleiben. Die Website ist groß und hat bereits Tausende von Benutzern; Es handelt sich nicht um einen schnellen Prototyp für die Investoren. Ich muss also sicherstellen, dass der Code lesbar ist, die API Sinn macht, benutzbar und wartbar ist und keine offensichtlichen Fußwaffen enthält.

  • Das Unternehmen legt Wert auf sein Image und die Qualität seiner Website – warum sollte es sonst überhaupt ein Redesign durchführen? (Nehmen wir hier eine positive Absicht an?). Das bedeutet, dass ein gewisses Maß an Qualität erwartet wird und ich vorausschauend denken und gängige Szenarien und Grenzfälle antizipieren muss, auch wenn sie noch nicht Teil des aktuellen Designs sind.

  • Viele Benutzer meinen wahrscheinlich, dass nicht alle ausschließlich die Maus verwenden, um mit der Website zu interagieren. Der Dialog muss auch über Tastaturinteraktionen und möglicherweise sogar unterstützende Technologien wie Bildschirmleseprogramme verfügbar sein.

  • Eine große vorhandene Codebasis (es handelt sich um eine Neugestaltung, denken Sie daran!) bedeutet, dass es wahrscheinlich Einschränkungen hinsichtlich der externen Abhängigkeiten gibt, die ich für diese Funktion mitbringen kann. Jede externe Abhängigkeit hat ihren Preis, insbesondere bei großen und alten Codebasen. Für den Zweck des Artikels gehen wir davon aus, dass ich eine externe Bibliothek nutzen kann, dafür bräuchte ich aber eine gute Begründung.

  • Endlich kommen weitere Designs, daher muss ich vorhersehen, in welche Richtung es aus Design- und Benutzersicht gehen kann, und sicherstellen, dass der Code frühzeitig damit umgehen kann.

Schritt 3: Konsolidieren Sie die Modal Dialog API

Da ich nun die Anforderungen kenne und vernünftige Vermutungen habe, kann ich die eigentliche Dialogkomponente erstellen. Zunächst einmal aus diesem Code:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich muss den Dialogteil unbedingt in eine wiederverwendbare Komponente extrahieren – es müssen viele dialogbasierte Funktionen implementiert werden.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der Dialog verfügt über eine onClose-Requisite – sie benachrichtigt die übergeordnete Komponente, wenn auf die Schaltfläche „Schließen“ oder den Hintergrund geklickt wird. Die übergeordnete Komponente hat dann immer noch den Status und stellt den Dialog wie folgt dar:

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Jetzt schauen wir uns noch einmal das Design an und denken etwas mehr über Dialoge nach:

Existential React questions and a perfect Modal Dialog

Es wird offensichtlich einen „Fußzeilen“-Teil des Dialogs mit Aktionsschaltflächen geben. Höchstwahrscheinlich wird es viele Variationen dieser Schaltflächen geben – eine, zwei, drei, linksbündig, rechtsbündig, mit Leerzeichen dazwischen usw. Außerdem hat dieser Dialog keine Kopfzeile, aber Es ist sehr, sehr wahrscheinlich, dass dies der Fall ist – Dialoge mit einigen Headern sind ein ziemlich häufiges Muster. Hier wird es auf jeden Fall einen Inhalts-Bereich mit völlig zufälligen Inhalten geben – von reinem Bestätigungstext über Formulare und interaktive Erlebnisse bis hin zu sehr langen scrollbaren „Allgemeinen Geschäftsbedingungen“-Texten, die niemand liest.

Zum Schluss noch die Größe. Der Dialog im Design ist winzig, nur ein Bestätigungsdialog. Große Formulare oder lange Texte passen dort nicht hin. Angesichts der Informationen, die wir in Schritt 2 gesammelt haben, kann man also mit ziemlicher Sicherheit davon ausgehen, dass die Größe des Dialogs geändert werden muss. Da die Designer wahrscheinlich Designrichtlinien haben, können wir derzeit davon ausgehen, dass wir drei Variationen des Dialogs haben werden: „klein“, „mittel“ und „groß“.

All dies bedeutet, dass wir Requisiten für den ModalDialog benötigen: Fußzeile und Kopfzeile sind nur normale Requisiten, die ReactNode akzeptieren, die Größe ist nur eine Vereinigung von Zeichenfolgen und der Inhaltsbereich wird als Hauptteil verwendet Kinder:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir steuern die Größe des Dialogs mit einem zusätzlichen Klassennamen, der von den Requisiten stammt. Im wirklichen Leben hängt es jedoch stark von der Styling-Lösung ab, die im Repo verwendet wird.

Allerdings ist der Dialog in dieser Variante einfach zu flexibel – so ziemlich alles kann überall hingehen. In der Fußzeile zum Beispiel können wir meistens nur ein oder zwei Schaltflächen erwarten, mehr nicht. Und diese Schaltflächen müssten überall auf der Website einheitlich angeordnet sein. Wir brauchen einen Wrapper, der sie ausrichtet:

.backdrop {
  background: rgba(0, 0, 0, 0.3);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
Nach dem Login kopieren
Nach dem Login kopieren

Das Gleiche gilt für den Inhalt – zumindest bräuchte er etwas Polsterung rundherum und die Möglichkeit zum Scrollen. Und die Kopfzeile benötigt möglicherweise einige Stile für den Text. Das Layout sieht also so aus:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Aber leider können wir das nicht garantieren. Es ist sehr wahrscheinlich, dass irgendwann jemand mehr in der Fußzeile haben möchte als nur Schaltflächen. Oder einige der Dialoge müssten einen Header auf einem verkauften Hintergrund haben. Oder manchmal benötigt der Inhalt keine Polsterung.

Was ich damit meine, ist, dass wir eines Tages in der Lage sein müssen, den Kopf-/Inhalts-/Fußzeilenteil zu formatieren. Und wahrscheinlich früher als erwartet.

Wir könnten diese Konfiguration natürlich einfach mit Requisiten übergeben und so etwas wie HeaderClassName-, ContentClassName- und FooterClassName-Requisiten haben. Und in einigen Fällen könnte es tatsächlich in Ordnung sein. Aber für so etwas wie den netten Dialog für das schöne Redesign könnten wir es besser machen.

Eine wirklich gute Möglichkeit, dieses Problem zu lösen, besteht darin, unsere Kopf-/Inhalts-/Fußzeile in eigene Komponenten zu extrahieren, wie folgt:

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

und setzen Sie den ModalDialog-Code auf den Code ohne die Wrapper zurück:

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Auf diese Weise würde ich in der übergeordneten App, wenn ich das Standarddesign für die Dialogteile haben möchte, diese winzigen Komponenten verwenden:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Und wenn ich etwas völlig Benutzerdefiniertes haben wollte, würde ich eine neue Komponente mit ihren eigenen benutzerdefinierten Stilen implementieren, ohne mit dem ModalDialog selbst herumzuspielen:

.backdrop {
  background: rgba(0, 0, 0, 0.3);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
Nach dem Login kopieren
Nach dem Login kopieren

Im Übrigen brauche ich nicht einmal mehr die Kopf- und Fußzeilen-Requisite. Ich kann den DialogHeader und den DialogFooter einfach an die Kinder weitergeben, den ModalDialog noch mehr vereinfachen und eine noch schönere API mit dem gleichen Maß an Flexibilität haben, während ich überall ein einheitliches Design habe.

Die übergeordnete Komponente sieht dann so aus:

.dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
Nach dem Login kopieren

Und die API des Dialogs sieht so aus:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Ich bin bisher ziemlich zufrieden damit. Es ist flexibel genug, um das Design beliebig zu erweitern, aber auch klar und sinnvoll genug, um problemlos eine einheitliche Benutzeroberfläche in der gesamten App zu implementieren.

Hier ist das Live-Beispiel zum Herumspielen:

Schritt 4: Leistung und erneutes Rendern

Jetzt, da die Modal-API in einem ausreichend guten Zustand ist, ist es an der Zeit, sich mit der offensichtlichen Fußwaffe zu befassen, die ich implementiert habe. Wenn Sie genug von meinen Artikeln gelesen haben, haben Sie wahrscheinlich laut geschrien: „Was machst du??? Neu rendern!!“ für die letzten zehn Minuten? Und natürlich hast du recht:

const ModalDialog = ({ onClose }) => {
  return (
    <>
      <div className="backdrop" onClick={onClose}></div>
      <div className="dialog">
        <button className="close-button" onClick={onClose}>
          Close
        </button>
      </div>
    </>
  );
};
Nach dem Login kopieren

Die Seitenkomponente hier hat den Status. Jedes Mal, wenn das Modal geöffnet oder geschlossen ist, ändert sich der Zustand und es kommt zu einem erneuten Rendern der gesamten Komponente und aller darin enthaltenen Elemente. Ja, „vorzeitige Optimierung ist die Wurzel allen Übels“ und ja, optimieren Sie die Leistung nicht, bevor Sie sie tatsächlich gemessen haben. In diesem Fall können wir die gängige Meinung getrost ignorieren.

Aus zwei Gründen. Erstens weiß ich mit Sicherheit, dass in der App viele Modalitäten verstreut sein werden. Es handelt sich nicht um eine einmalige versteckte Funktion, die niemand nutzen wird. Daher ist die Wahrscheinlichkeit, dass jemand mit einer API wie dieser einen Zustand an eine Stelle setzt, an der er nicht sein sollte, recht hoch. Und zweitens erfordert es nicht viel Zeit und Mühe, um zu verhindern, dass das Problem des erneuten Renderns überhaupt auftritt. Nur 1 Minute Aufwand, und wir müssen hier überhaupt nicht an die Leistung denken.

Alles, was wir tun müssen, ist, den Zustand zu kapseln und die Idee einer „unkontrollierten Komponente“ einzuführen:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wobei der BaseModalDialog genau derselbe Dialog ist, den wir zuvor hatten, ich habe ihn nur umbenannt.

Und dann übergeben Sie eine Komponente, die den Dialog auslösen soll, als Trigger-Requisite:

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Die Seitenkomponente sieht dann folgendermaßen aus:

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Kein Status innerhalb der Seite mehr, keine potenziell gefährlichen erneuten Renderings mehr.

Eine solche API sollte 95 % der Anwendungsfälle abdecken, da ein Benutzer in den meisten Fällen auf etwas klicken muss, damit der Dialog angezeigt wird. In seltenen Situationen, in denen ein Dialog unabhängig angezeigt werden muss, beispielsweise auf einer Verknüpfung oder als Teil des Onboardings, kann ich trotzdem den BaseModalDialog verwenden und den Status manuell bearbeiten.

Schritt 5: Umgang mit Randfällen und Zugänglichkeit

Die API der ModalDialog-Komponente ist aus der React-Perspektive ziemlich solide, aber die Arbeit ist noch lange nicht erledigt. Angesichts der Must-Haves, die ich in Schritt 2 gesammelt habe, muss ich noch ein paar weitere Probleme beheben.

Problem 1: Ich binde den Trigger in einen zusätzlichen Bereich ein – in bestimmten Fällen kann das das Layout einer Seite beschädigen. Ich muss die Hülle irgendwie loswerden.

Problem 2: Wenn ich den Dialog innerhalb eines Elements rendere, das einen neuen Stapelkontext erstellt, wird das Modal unter einigen Elementen angezeigt. Ich muss es innerhalb eines Portals rendern, nicht direkt im Layout, wie ich es jetzt tue.

Problem 3: Der Tastaturzugriff ist derzeit ziemlich schlecht. Wenn ein ordnungsgemäß implementierter modaler Dialog geöffnet wird, sollte der Fokus nach innen springen. Wenn es geschlossen ist, sollte der Fokus auf das Element zurückkehren, das den Dialog ausgelöst hat. Wenn der Dialog geöffnet ist, sollte der Fokus im Inneren „gefangen“ sein und die Elemente außerhalb sollten nicht fokussierbar sein. Durch Drücken der ESC-Taste sollte der Dialog geschlossen werden. Nichts davon ist derzeit umgesetzt.

Die Probleme 1 und 2 sind etwas ärgerlich, können aber relativ schnell gelöst werden. Problem 3 ist jedoch manuell sehr mühsam zu erledigen. Außerdem handelt es sich sicherlich um ein gelöstes Problem – jeder Dialog überall auf der Welt würde diese Funktionalität benötigen.

Die Kombination „riesiger Aufwand, den ich selbst erledigen muss“ „Sieht nach einem sicherlich gelösten Problem aus“ ist der Punkt, an dem ich nach einer bestehenden Bibliothek suchen würde.

In Anbetracht all der Vorarbeiten, die ich bereits geleistet habe, ist es jetzt einfach, das Richtige auszuwählen.

Ich könnte auf alle vorhandenen UI-Komponentenbibliotheken wie Ant Design oder Material UI zurückgreifen und von dort aus einen Dialog verwenden. Aber wenn die Neugestaltung sie nicht nutzt, wird die Anpassung ihrer Designs an die, die ich brauche, mehr Ärger mit sich bringen als sie lösen. Daher ist es für diesen Fall ein sofortiges NEIN.

Ich könnte eine der „kopflosen“ UI-Bibliotheken wie Radix oder React Aria verwenden. Diese implementieren die Funktionalität wie Status und Trigger sowie die gesamte Zugänglichkeit, überlassen das Design jedoch dem Verbraucher. Wenn ich mir ihre API ansehe, müsste ich noch einmal überprüfen, ob sie mir ermöglichen, den Status des Dialogs zu steuern, wenn ich es wirklich brauche, für die Fälle, in denen ich den Dialog manuell auslösen möchte (was sie tun).

Wenn ich die Headless-Bibliotheken aus irgendeinem Grund nicht verwenden kann, würde ich zumindest versuchen, eine Bibliothek zu verwenden, die die Focus-Trap-Funktionalität übernimmt.

Nehmen wir für den Artikel an, dass ich jede Bibliothek mitbringen kann, die ich möchte. In diesem Fall wähle ich Radix – es ist sehr einfach zu verwenden und die API des Dialogs sieht dem, was ich bereits implementiert habe, sehr ähnlich, sodass die Umgestaltung ein Kinderspiel sein sollte.

Wir müssten die API des Dialogs selbst ein wenig ändern:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Es ist so ziemlich das Gleiche wie vorher. Nur verwende ich anstelle von Divs überall Radix-Primitive.

Die unkontrollierte Dialognutzung ändert sich überhaupt nicht:

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Und der kontrollierte Dialog ändert sich geringfügig – ich müsste ihm Requisiten übergeben, anstatt bedingtes Rendering:

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Sehen Sie sich das Beispiel unten an und versuchen Sie, die Tastatur zum Navigieren zu verwenden. Alles funktioniert so, wie ich es brauche, wie cool ist das?

Als Bonus kümmert sich Radix auch um das Portal-Problem und schließt Trigger nicht in eine Spanne ein. Ich muss keine Randfälle mehr lösen, daher kann ich mit dem letzten Schritt fortfahren.

Schritt 6: Endpolitur

Die Funktion ist noch nicht fertig! ? Der Dialog sieht jetzt ziemlich solide aus und fühlt sich auch so an, daher werde ich zu diesem Zeitpunkt keine wesentlichen Änderungen an seiner Implementierung vornehmen. Aber es bedarf noch einiger Dinge, um als „perfekter“ Dialog für den Anwendungsfall, den ich löse, zu gelten.

Eins: Das allererste, worum die Designer mich bitten werden, falls sie es noch nicht getan haben, ist das Hinzufügen einer subtilen Animation für das Öffnen des Dialogs. Ich müsste es vorhersehen und mich daran erinnern, wie man Animationen in React erstellt.

Zwei: Ich müsste dem Dialog maximale Breite und maximale Höhe hinzufügen, damit er auf kleinen Bildschirmen immer noch anständig aussieht. Und überlegen Sie, wie es auf sehr großen Bildschirmen aussehen würde.

Drei: Ich müsste mit den Designern darüber sprechen, wie sich der Dialog auf Mobilgeräten verhalten soll. Die Chancen stehen gut, dass sie mich bitten werden, daraus ein Einschubfenster zu machen, das unabhängig von der Größe des Dialogs den größten Teil des Bildschirms einnimmt.

Vier: Ich müsste mindestens die Komponenten DialogTitle und DialogDescription einführen – Radix wird Sie aus Gründen der Barrierefreiheit bitten, sie zu verwenden.

Fünf: Tests! Der Dialog bleibt bestehen und wird von anderen Personen aufrechterhalten, daher sind Tests in diesem Fall so gut wie obligatorisch.

Und wahrscheinlich noch jede Menge andere kleine Dinge, die ich jetzt vergessen habe und die später auftauchen werden. Ganz zu schweigen von der Umsetzung der eigentlichen Designs für den Inhalt des Dialogs.

Noch ein paar Gedanken

Wenn Sie den „Dialog“ oben durch „SomeNewFeature“ ersetzen, ist dies mehr oder weniger der Algorithmus, den ich verwende, um so ziemlich alles Neue zu implementieren.

Schnelle „Spitze“ der Lösung(en) → Anforderungen für die Funktion sammeln → dafür sorgen, dass sie funktioniert → sie leistungsfähig machen → sie vervollständigen → sie perfekt machen.

Für so etwas wie den eigentlichen Dialog, den ich mittlerweile hunderte Male umgesetzt habe, mache ich den ersten Schritt in 10 Sekunden im Kopf und beginne gleich mit Schritt 2.

Für etwas sehr Kompliziertes und Unbekanntes kann Schritt 1 länger dauern und das sofortige Erkunden verschiedener Lösungen und Bibliotheken erfordern.

Etwas, das nicht ganz unbekannt ist, sondern nur eine „normale Funktion, die wir ausführen müssen“, könnte Schritt 1 überspringen, da es möglicherweise nichts zu erkunden gibt.

Sehr oft, insbesondere in „agilen“ Umgebungen, wird es eher eine Spirale als eine gerade Linie sein, in der Anforderungen schrittweise bereitgestellt werden und sich oft ändern, und wir kehren regelmäßig zu den ersten beiden Schritten zurück.


Ich hoffe, diese Art von Artikel war nützlich! ?? Lassen Sie mich wissen, ob Sie mehr Inhalte wie diesen haben möchten oder lieber das übliche „Wie die Dinge funktionieren“-Zeug bevorzugen.

Und ich freue mich darauf zu hören, wie sich dieser Prozess in euren Köpfen unterscheidet?


Ursprünglich veröffentlicht unter https://www.developerway.com. Auf der Website gibt es noch mehr Artikel wie diesen ?

Schauen Sie sich das Advanced React-Buch an, um Ihr React-Wissen auf die nächste Stufe zu bringen.

Abonnieren Sie den Newsletter, verbinden Sie sich auf LinkedIn oder folgen Sie auf Twitter, um benachrichtigt zu werden, sobald der nächste Artikel erscheint.


Und übrigens noch eine letzte Sache: Wenn Sie bald mit einem neuen Projekt beginnen und keinen Designer und die Zeit haben, das Designerlebnis wie beschrieben zu verfeinern – ich habe vor kurzem Stunden um Stunden (und Stunden) damit verbracht, ein neues Projekt zu implementieren Bibliothek von UI-Komponenten für diesen Fall. Es verfügt über kopier- und einfügbare Komponenten und gängige Muster, Radix und Tailwind, Dunkelmodus, Barrierefreiheit und sofort einsatzbereite mobile Unterstützung. Einschließlich des perfekten modalen Dialogs oben! ?

Probieren Sie es aus: https://www.buckets-ui.com/

Existential React questions and a perfect Modal Dialog

Das obige ist der detaillierte Inhalt vonExistenzielle Reaktionsfragen und ein perfekter modaler Dialog. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage