Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Beginn unserer React ToDo-App

Angenommen, uns wurde die Aufgabe gegeben, ein Proof-of-Concept in React zu erstellen – eine App, die es Benutzern ermöglicht, Aufgaben hinzuzufügen, zu bearbeiten und zu löschen, an denen sie arbeiten möchten, und auch Aufgaben als erledigt zu markieren, ohne sie zu löschen. Dieser Artikel führt Sie durch die grundlegende Struktur und das Styling einer solchen Anwendung, die bereit ist für die Definition individueller Komponenten und Interaktivität, die wir später hinzufügen werden.

Hinweis: Wenn Sie Ihren Code mit unserer Version abgleichen müssen, finden Sie eine fertige Version des Beispiel-React-App-Codes in unserem todo-react repository. Für eine live laufende Version siehe https://mdn.github.io/todo-react/.

Voraussetzungen: Vertrautheit mit den Kernsprachen HTML, CSS und JavaScript sowie dem Terminal/Kommandozeile.
Lernziele: Vertrautheit mit unserer ToDo-Listen-Fallstudie und die grundlegende App-Struktur und Stilgebung.

Die User Stories unserer App

In der Softwareentwicklung ist eine User Story ein umsetzbares Ziel aus der Sicht des Benutzers. Das Definieren von User Stories, bevor wir unsere Arbeit beginnen, wird uns helfen, unsere Arbeit zu fokussieren. Unsere App sollte die folgenden Geschichten erfüllen:

Als Benutzer kann ich

  • eine Liste von Aufgaben lesen.
  • eine Aufgabe mit der Maus oder Tastatur hinzufügen.
  • eine beliebige Aufgabe als erledigt markieren, mit der Maus oder Tastatur.
  • eine beliebige Aufgabe löschen, mit der Maus oder Tastatur.
  • eine beliebige Aufgabe bearbeiten, mit der Maus oder Tastatur.
  • eine bestimmte Teilmenge von Aufgaben anzeigen: Alle Aufgaben, nur die aktive Aufgabe oder nur die erledigten Aufgaben.

Wir werden diese Geschichten einzeln angehen.

Vorbereitungen vor dem Projekt

Vite hat uns einige Codes gegeben, die wir für unser Projekt überhaupt nicht verwenden werden. Die folgenden Terminalbefehle löschen diese, um Platz für unser neues Projekt zu schaffen. Stellen Sie sicher, dass Sie sich im Stammverzeichnis der App befinden!

bash
# Move into the src directory
cd src
# Delete the App.css file and the React logo provided by Vite
rm App.css assets/react.svg
# Empty the contents of App.jsx and index.css
echo -n > App.jsx && echo -n > index.css
# Move back up to the root of the project
cd ..

Hinweis: Wenn Sie Ihren Server angehalten haben, um die oben genannten Terminalaufgaben auszuführen, müssen Sie ihn erneut starten, indem Sie npm run dev verwenden.

Projekt-Startercode

Als Ausgangspunkt für dieses Projekt werden wir zwei Dinge bereitstellen: eine App()-Funktion, um die zuvor gelöschte zu ersetzen, und etwas CSS, um Ihre App zu gestalten.

Das JSX

Kopieren Sie den folgenden Ausschnitt in Ihre Zwischenablage und fügen Sie ihn dann in App.jsx ein:

jsx
function App(props) {
  return (
    <div className="todoapp stack-large">
      <h1>TodoMatic</h1>
      <form>
        <h2 className="label-wrapper">
          <label htmlFor="new-todo-input" className="label__lg">
            What needs to be done?
          </label>
        </h2>
        <input
          type="text"
          id="new-todo-input"
          className="input input__lg"
          name="text"
          autoComplete="off"
        />
        <button type="submit" className="btn btn__primary btn__lg">
          Add
        </button>
      </form>
      <div className="filters btn-group stack-exception">
        <button type="button" className="btn toggle-btn" aria-pressed="true">
          <span className="visually-hidden">Show </span>
          <span>all</span>
          <span className="visually-hidden"> tasks</span>
        </button>
        <button type="button" className="btn toggle-btn" aria-pressed="false">
          <span className="visually-hidden">Show </span>
          <span>Active</span>
          <span className="visually-hidden"> tasks</span>
        </button>
        <button type="button" className="btn toggle-btn" aria-pressed="false">
          <span className="visually-hidden">Show </span>
          <span>Completed</span>
          <span className="visually-hidden"> tasks</span>
        </button>
      </div>
      <h2 id="list-heading">3 tasks remaining</h2>
      <ul
        role="list"
        className="todo-list stack-large stack-exception"
        aria-labelledby="list-heading">
        <li className="todo stack-small">
          <div className="c-cb">
            <input id="todo-0" type="checkbox" defaultChecked />
            <label className="todo-label" htmlFor="todo-0">
              Eat
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">Eat</span>
            </button>
            <button type="button" className="btn btn__danger">
              Delete <span className="visually-hidden">Eat</span>
            </button>
          </div>
        </li>
        <li className="todo stack-small">
          <div className="c-cb">
            <input id="todo-1" type="checkbox" />
            <label className="todo-label" htmlFor="todo-1">
              Sleep
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">Sleep</span>
            </button>
            <button type="button" className="btn btn__danger">
              Delete <span className="visually-hidden">Sleep</span>
            </button>
          </div>
        </li>
        <li className="todo stack-small">
          <div className="c-cb">
            <input id="todo-2" type="checkbox" />
            <label className="todo-label" htmlFor="todo-2">
              Repeat
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">Repeat</span>
            </button>
            <button type="button" className="btn btn__danger">
              Delete <span className="visually-hidden">Repeat</span>
            </button>
          </div>
        </li>
      </ul>
    </div>
  );
}

export default App;

Öffnen Sie nun index.html und ändern Sie den Text des <title>-Elements in TodoMatic. Auf diese Weise wird es mit dem <h1> oben in unserer App übereinstimmen.

html
<title>TodoMatic</title>

Wenn Ihr Browser aktualisiert wird, sollten Sie so etwas sehen:

todo-matic App, ungestylt, zeigt ein Durcheinander von Labels, Eingaben und Buttons

Es ist hässlich und funktioniert noch nicht, aber das ist in Ordnung – wir werden es in einem Moment stylen. Betrachten Sie zunächst das JSX, das wir haben, und wie es mit unseren User Stories übereinstimmt:

  • Wir haben ein <form>-Element mit einem <input type="text"> zum Schreiben einer neuen Aufgabe und einem Button, um das Formular abzusenden.
  • Wir haben eine Reihe von Buttons, die verwendet werden, um unsere Aufgaben zu filtern.
  • Wir haben eine Überschrift, die angibt, wie viele Aufgaben noch ausstehen.
  • Wir haben unsere 3 Aufgaben, angeordnet in einer ungeordneten Liste. Jede Aufgabe ist ein Listenelement (<li>) und verfügt über Buttons zum Bearbeiten und Löschen sowie ein Kontrollkästchen, um sie als erledigt abzuhaken.

Das Formular ermöglicht es uns, Aufgaben zu erstellen; die Buttons lassen uns Aufgaben filtern; die Überschrift und die Liste sind unser Weg, um sie zu lesen. Die Benutzeroberfläche zum Bearbeiten einer Aufgabe fehlt derzeit auffällig. Das ist okay – wir werden das später schreiben.

Zugänglichkeitsfunktionen

Vielleicht bemerken Sie hier ein ungewöhnliches Markup. Beispielsweise:

jsx
<button type="button" className="btn toggle-btn" aria-pressed="true">
  <span className="visually-hidden">Show </span>
  <span>all</span>
  <span className="visually-hidden"> tasks</span>
</button>

Hier informiert aria-pressed assistive Technologien (wie Bildschirmleseprogramme), dass der Button in zwei Zuständen sein kann: pressed oder unpressed. Denken Sie an diese als Analoga für on und off. Eine Einstellung mit dem Wert "true" bedeutet, dass der Button standardmäßig gedrückt ist.

Die Klasse visually-hidden hat bisher keine Wirkung, weil wir noch kein CSS integriert haben. Sobald wir unsere Styles eingerichtet haben, wird jedes Element mit dieser Klasse Benutzern mit Sehvermögen verborgen und weiterhin für assistive Technologien verfügbar sein — denn diese Wörter werden nicht von sehenden Benutzern benötigt; sie sind da, um assistiven Technologiebenutzern, die den visuellen Kontext nicht haben, zusätzliche Informationen darüber zu geben, was der Button macht.

Weiter unten finden Sie unser <ul>-Element:

html
<ul
  role="list"
  className="todo-list stack-large stack-exception"
  aria-labelledby="list-heading">
  …
</ul>

Das Attribut role unterstützt assistive Technologien dabei zu erklären, um welche Art von Element es sich bei einem Tag handelt. Ein <ul> wird standardmäßig wie eine Liste behandelt, aber die Stile, die wir gleich hinzufügen werden, brechen diese Funktionalität. Diese Rolle wird die "Liste"-Bedeutung für das <ul>-Element wiederherstellen. Wenn Sie mehr darüber erfahren möchten, warum dies notwendig ist, können Sie Scott O'Haras Artikel, "Fixing Lists" lesen.

Das Attribut aria-labelledby sagt assistiven Technologien, dass wir unsere Listenüberschrift als Beschriftung verwenden, die den Zweck der darunter liegenden Liste beschreibt. Diese Assoziation gibt der Liste einen informativen Kontext, der assistive Technologiebenutzer unterstützen könnte, den Zweck der Liste besser zu verstehen.

Schließlich haben die Labels und Eingaben in unseren Listenelementen einige Attribute, die für JSX einzigartig sind:

jsx
<div className="c-cb">
  <input id="todo-0" type="checkbox" defaultChecked />
  <label className="todo-label" htmlFor="todo-0">
    Eat
  </label>
</div>

Das Attribut defaultChecked im <input />-Tag sagt React, dass dieses Kontrollkästchen initial markiert sein soll. Wenn wir checked verwenden würden, wie wir es in regulärem HTML tun, würde React einige Warnungen in unsere Browserkonsole protokollieren, die sich auf die Ereignisverwaltung am Kontrollkästchen beziehen, was wir vermeiden möchten. Machen Sie sich darüber vorerst keine Sorgen — wir werden dies später behandeln, wenn wir zu der Verwendung von Ereignissen kommen.

Das Attribut htmlFor entspricht dem Attribut for, das in HTML verwendet wird. Wir können for nicht als Attribut in JSX verwenden, da for ein reserviertes Wort ist. Daher verwendet React stattdessen htmlFor.

Eine Anmerkung zu booleschen Attributen in JSX

Das Attribut defaultChecked im vorherigen Abschnitt ist ein boolesches Attribut – ein Attribut, dessen Wert entweder true oder false ist. Wie in HTML ist ein boolesches Attribut true, wenn es vorhanden ist, und false, wenn es nicht vorhanden ist; die Zuweisung auf der rechten Seite des Ausdrucks ist optional. Sie können seinen Wert explizit setzen, indem Sie ihn in geschweifte Klammern setzen – zum Beispiel defaultChecked={true} oder defaultChecked={false}.

Da JSX JavaScript ist, gibt es eine Besonderheit bei booleschen Attributen: das Schreiben von defaultChecked="false" setzt einen string Wert von "false" anstelle eines booleschen Wertes. Nicht-leere Strings sind wahrheitsgemäß, daher wird React defaultChecked als true betrachten und das Kontrollkästchen standardmäßig markieren. Das ist nicht das, was wir wollen, daher sollten wir dies vermeiden.

Wenn Sie möchten, können Sie das Schreiben von booleschen Attributen mit einem anderen Attribut üben, das Ihnen vielleicht schon einmal begegnet ist, hidden, welches verhindert, dass Elemente auf der Seite gerendert werden. Versuchen Sie hidden dem <h1>-Element in App.jsx hinzuzufügen, um zu sehen, was passiert. Versuchen Sie dann, seinen Wert explizit auf {false} zu setzen. Beachten Sie erneut, dass das Schreiben von hidden="false" zu einem wahrheitsgemäßen Wert führt, sodass der <h1> dennoch ausgeblendet wird. Vergessen Sie nicht, diesen Code zu entfernen, wenn Sie fertig sind.

Hinweis: Das in unserem früheren Code-Snippet verwendete Attribut aria-pressed hat den Wert "true", da aria-pressed kein echtes boolesches Attribut ist, so wie checked es ist.

Umsetzung unserer Styles

Fügen Sie den folgenden CSS-Code in src/index.css ein:

css
/* Resets */
*,
*::before,
*::after {
  box-sizing: border-box;
}
*:focus-visible {
  outline: 3px dashed #228bec;
  outline-offset: 0;
}
html {
  font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
  margin-bottom: 0;
}
ul {
  list-style: none;
  padding: 0;
}
button {
  -moz-osx-font-smoothing: inherit;
  -webkit-font-smoothing: inherit;
  appearance: none;
  background: transparent;
  border: none;
  color: inherit;
  font: inherit;
  line-height: normal;
  margin: 0;
  overflow: visible;
  padding: 0;
  width: auto;
}
button::-moz-focus-inner {
  border: 0;
}
button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  overflow: visible;
}
input[type="text"] {
  border-radius: 0;
}
body {
  background-color: whitesmoke;
  color: #4d4d4d;
  font:
    1.6rem/1.25 "Arial",
    sans-serif;
  margin: 0 auto;
  max-width: 68rem;
  width: 100%;
}
@media screen and (width >= 620px) {
  body {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}
/* End resets */
/* Global styles */
.form-group > input[type="text"] {
  display: inline-block;
  margin-top: 0.4rem;
}
.btn {
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  padding: 0.8rem 1rem 0.7rem;
  text-transform: capitalize;
}
.btn.toggle-btn {
  border-color: lightgray;
  border-width: 1px;
}
.btn.toggle-btn[aria-pressed="true"] {
  border-color: #4d4d4d;
  text-decoration: underline;
}
.btn__danger {
  background-color: #ca3c3c;
  border-color: #bd2130;
  color: white;
}
.btn__filter {
  border-color: lightgrey;
}
.btn__primary {
  background-color: black;
  color: white;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > * {
  flex: 1 1 49%;
}
.btn-group > * + * {
  margin-left: 0.8rem;
}
.label-wrapper {
  flex: 0 0 100%;
  margin: 0;
  text-align: center;
}
.visually-hidden {
  clip: rect(1px, 1px, 1px, 1px);
  height: 1px;
  overflow: hidden;
  position: absolute !important;
  white-space: nowrap;
  width: 1px;
}
[class*="stack"] > * {
  margin-bottom: 0;
  margin-top: 0;
}
.stack-small > * + * {
  margin-top: 1.25rem;
}
.stack-large > * + * {
  margin-top: 2.5rem;
}
@media screen and (width >= 550px) {
  .stack-small > * + * {
    margin-top: 1.4rem;
  }
  .stack-large > * + * {
    margin-top: 2.8rem;
  }
}
.stack-exception {
  margin-top: 1.2rem;
}
/* End global styles */
/* General app styles */
.todoapp {
  background: white;
  box-shadow:
    0 2px 4px 0 rgb(0 0 0 / 20%),
    0 2.5rem 5rem 0 rgb(0 0 0 / 10%);
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  position: relative;
}
@media screen and (width >= 550px) {
  .todoapp {
    padding: 4rem;
  }
}
.todoapp > * {
  margin-left: auto;
  margin-right: auto;
  max-width: 50rem;
}
.todoapp > form {
  max-width: 100%;
}
.todoapp > h1 {
  display: block;
  margin: 0;
  margin-bottom: 1rem;
  max-width: 100%;
  text-align: center;
}
.label__lg {
  line-height: 1.01567;
  font-weight: 300;
  margin-bottom: 1rem;
  padding: 0.8rem;
  text-align: center;
}
.input__lg {
  border: 2px solid black;
  padding: 2rem;
}
.input__lg:focus-visible {
  border-color: #4d4d4d;
  box-shadow: inset 0 0 0 2px;
}
[class*="__lg"] {
  display: inline-block;
  font-size: 1.9rem;
  width: 100%;
}
[class*="__lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (width >= 620px) {
  [class*="__lg"] {
    font-size: 2.4rem;
  }
}
/* End general app styles */
/* Todo item styles */
.todo {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
.todo > * {
  flex: 0 0 100%;
}
.todo-text {
  border: 2px solid #565656;
  min-height: 4.4rem;
  padding: 0.4rem 0.8rem;
  width: 100%;
}
.todo-text:focus-visible {
  box-shadow: inset 0 0 0 2px;
}
/* End todo item styles */
/* Checkbox styles */
.c-cb {
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
  clear: left;
  display: block;
  font-family: "Arial", sans-serif;
  font-size: 1.6rem;
  font-weight: 400;
  line-height: 1.25;
  min-height: 44px;
  padding-left: 40px;
  position: relative;
}
.c-cb > label::before,
.c-cb > input[type="checkbox"] {
  box-sizing: border-box;
  height: 44px;
  left: -2px;
  top: -2px;
  width: 44px;
}
.c-cb > input[type="checkbox"] {
  -webkit-font-smoothing: antialiased;
  cursor: pointer;
  margin: 0;
  opacity: 0;
  position: absolute;
  z-index: 1;
}
.c-cb > label {
  cursor: pointer;
  display: inline-block;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  margin-bottom: 0;
  padding: 8px 15px 5px;
  touch-action: manipulation;
}
.c-cb > label::before {
  background: transparent;
  border: 2px solid currentColor;
  content: "";
  position: absolute;
}
.c-cb > input[type="checkbox"]:focus-visible + label::before {
  border-width: 4px;
  outline: 3px dashed #228bec;
}
.c-cb > label::after {
  background: transparent;
  border: solid;
  border-width: 0 0 5px 5px;
  border-top-color: transparent;
  box-sizing: content-box;
  content: "";
  height: 7px;
  left: 9px;
  opacity: 0;
  position: absolute;
  top: 11px;
  transform: rotate(-45deg);
  width: 18px;
}
.c-cb > input[type="checkbox"]:checked + label::after {
  opacity: 1;
}
/* End checkbox styles */

Speichern Sie und schauen Sie in Ihren Browser zurück, und Ihre App sollte jetzt eine angemessene Stilgebung haben.

Zusammenfassung

Jetzt sieht unsere ToDo-Listen-App tatsächlich etwas mehr wie eine echte App aus! Das Problem ist: Sie tut eigentlich nichts. Wir werden damit im nächsten Kapitel beginnen, das zu beheben!