Komponentenbildung unserer React-App
Im Moment ist unsere App ein Monolith. Bevor wir sie dazu bringen können, Dinge zu tun, müssen wir sie in handhabbare, beschreibende Komponenten zerlegen. React hat keine festen Regeln dafür, was eine Komponente ist und was nicht – das liegt bei Ihnen! In diesem Artikel zeigen wir Ihnen eine sinnvolle Möglichkeit, unsere App in Komponenten aufzuteilen.
Voraussetzungen: | Vertrautheit mit den Kernsprachen HTML, CSS und JavaScript, sowie der Terminal/Command Line. |
---|---|
Lernziele: | Eine sinnvolle Art, unsere Todo-Listen-App in Komponenten zu zerlegen. |
Definieren unserer ersten Komponente
Das Definieren einer Komponente kann knifflig erscheinen, bis Sie etwas Übung haben, aber das Wesentliche ist:
- Wenn es ein offensichtliches "Stück" Ihrer App darstellt, ist es wahrscheinlich eine Komponente
- Wenn es oft wiederverwendet wird, ist es wahrscheinlich eine Komponente.
Dieser zweite Punkt ist besonders wertvoll: Wenn Sie eine Komponente aus gemeinsamen UI-Elementen machen, können Sie Ihren Code an einer Stelle ändern und diese Änderungen überall dort sehen, wo die Komponente verwendet wird. Sie müssen nicht sofort alles in Komponenten aufbrechen. Lassen Sie uns vom zweiten Punkt inspirieren und eine Komponente aus dem am häufigsten wiederverwendeten, wichtigsten Teil der Benutzeroberfläche machen: einem Todo-Listenelement.
Erstelle ein <Todo />
Bevor wir eine Komponente erstellen können, sollten wir eine neue Datei dafür erstellen. Tatsächlich sollten wir ein Verzeichnis nur für unsere Komponenten erstellen. Stellen Sie sicher, dass Sie sich im Stammverzeichnis Ihrer App befinden, bevor Sie diese Befehle ausführen!
# create a `components` directory
mkdir src/components
# within `components`, create a file called `Todo.jsx`
touch src/components/Todo.jsx
Vergessen Sie nicht, Ihren Entwicklungsserver neu zu starten, wenn Sie ihn gestoppt haben, um die vorherigen Befehle auszuführen!
Lassen Sie uns eine Todo()
-Funktion in Todo.jsx
hinzufügen. Hier definieren wir eine Funktion und exportieren sie:
function Todo() {}
export default Todo;
Das ist soweit in Ordnung, aber unsere Komponente sollte etwas Nützliches zurückgeben! Gehen Sie zurück zu src/App.jsx
, kopieren Sie das erste <li>
aus der ungeordneten Liste und fügen Sie es in Todo.jsx
ein, sodass es folgendermaßen aussieht:
function Todo() {
return (
<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>
);
}
export default Todo;
Nun haben wir etwas, das wir verwenden können. Fügen Sie in App.jsx
die folgende Zeile oben in der Datei hinzu, um Todo
zu importieren:
import Todo from "./components/Todo";
Mit dieser importierten Komponente können Sie alle <li>
-Elemente in App.jsx
durch <Todo />
-Komponentenaufrufe ersetzen. Ihr <ul>
sollte folgendermaßen aussehen:
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
<Todo />
<Todo />
<Todo />
</ul>
Wenn Sie zu Ihrer App zurückkehren, werden Sie etwas Unangenehmes bemerken: Ihre Liste wiederholt jetzt dreimal die erste Aufgabe!
Wir wollen nicht nur essen; wir haben andere Dinge zu tun. Als nächstes schauen wir uns an, wie wir unterschiedliche Komponentenaufrufe eindeutige Inhalte rendern lassen können.
Erstellen eines eindeutigen <Todo />
Komponenten sind mächtig, weil sie es uns ermöglichen, Teile unserer Benutzeroberfläche wiederzuverwenden und auf einen einzigen Ort als Quelle dieser Benutzeroberfläche zu verweisen. Das Problem ist, dass wir in der Regel nicht jeden Teil der Komponente wiederverwenden wollen; wir möchten die meisten Teile wiederverwenden und kleine Stücke ändern. Hier kommen props
ins Spiel.
Was ist in einem name
?
Um die Namen der Aufgaben, die wir erledigen möchten, zu verfolgen, sollten wir sicherstellen, dass jede <Todo />
-Komponente einen eindeutigen Namen rendert.
Geben Sie jedem <Todo />
in App.jsx
eine name
-Prop. Verwenden wir die Namen unserer Aufgaben, die wir zuvor hatten:
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
<Todo name="Eat" />
<Todo name="Sleep" />
<Todo name="Repeat" />
</ul>
Wenn Ihr Browser aktualisiert wird, sehen Sie… genau dasselbe wie zuvor. Wir haben unserem <Todo />
einige Props gegeben, aber wir verwenden sie noch nicht. Gehen wir zurück zu Todo.jsx
und beheben das.
Ändern Sie zunächst Ihre Todo()
-Funktionsdefinition, sodass sie props
als Parameter nimmt. Sie können console.log()
verwenden, um Ihre Props zu überprüfen, wenn Sie sicher sein möchten, dass sie korrekt empfangen werden.
Sobald Sie sicher sind, dass Ihre Komponente ihre Props erhält, können Sie jede Vorkommen von Eat
durch Ihre name
-Prop ersetzen, indem Sie props.name
lesen. Denken Sie daran: props.name
ist ein JSX-Ausdruck, daher müssen Sie ihn in geschweifte Klammern setzen.
Alles zusammengefügt sollte Ihre Todo()
-Funktion so aussehen:
function Todo(props) {
return (
<li className="todo stack-small">
<div className="c-cb">
<input id="todo-0" type="checkbox" defaultChecked={true} />
<label className="todo-label" htmlFor="todo-0">
{props.name}
</label>
</div>
<div className="btn-group">
<button type="button" className="btn">
Edit <span className="visually-hidden">{props.name}</span>
</button>
<button type="button" className="btn btn__danger">
Delete <span className="visually-hidden">{props.name}</span>
</button>
</div>
</li>
);
}
export default Todo;
Jetzt sollte Ihr Browser drei eindeutige Aufgaben anzeigen. Ein weiteres Problem bleibt jedoch: Sie sind immer noch standardmäßig aktiviert.
Ist es completed
?
In unserer ursprünglichen statischen Liste war nur Eat
aktiviert. Wieder einmal möchten wir die meisten der UI, die eine <Todo />
-Komponente bildet, wiederverwenden, aber eine Sache ändern. Das ist wieder eine gute Aufgabe für eine weitere Prop! Geben Sie Ihrem ersten <Todo />
-Aufruf eine boolesche Prop von completed
und lassen Sie die anderen beiden unverändert.
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
<Todo name="Eat" completed />
<Todo name="Sleep" />
<Todo name="Repeat" />
</ul>
Wie zuvor müssen wir zu Todo.jsx
zurückgehen, um diese Props tatsächlich zu verwenden. Ändern Sie das defaultChecked
-Attribut auf dem <input />
, sodass sein Wert gleich der completed
-Prop ist. Sobald Sie fertig sind, liest das <input />
-Element der Todo-Komponente so:
<input id="todo-0" type="checkbox" defaultChecked={props.completed} />
Und Ihr Browser sollte aktualisieren, um nur Eat
als aktiviert zu zeigen:
Wenn Sie die completed
-Prop jeder <Todo />
-Komponente ändern, werden Ihre Browser die entsprechenden gerenderten Kontrollkästchen entsprechend aktivieren oder deaktivieren.
Geben Sie mir bitte etwas id
Wir haben noch ein_anderes_Problem: Unsere <Todo />
-Komponente gibt jeder Aufgabe ein id
-Attribut von todo-0
. Dies ist aus mehreren Gründen schlecht:
id
-Attribute müssen eindeutig sein (sie werden als eindeutige Bezeichner für Dokumentfragmente verwendet, von CSS, JavaScript usw.).- Wenn
id
s nicht eindeutig sind, kann die Funktionalität von Label-Elementen kaputtgehen.
Das zweite Problem betrifft unsere App im Moment. Wenn Sie auf das Wort "Sleep" neben dem zweiten Kontrollkästchen klicken, wird das Kontrollkästchen "Eat" umgeschaltet, anstatt das Kontrollkästchen "Sleep". Dies liegt daran, dass das <label>
-Element jedes Kontrollkästchens ein htmlFor
-Attribut von todo-0
hat. Die <label>
s erkennen nur das erste Element mit einem gegebenen id
-Attribut, was das Problem verursacht, das Sie sehen, wenn Sie auf die anderen Labels klicken.
Wir hatten einzigartige id
-Attribute, bevor wir die <Todo />
-Komponente erstellt haben. Lassen Sie uns diese zurückbringen, indem wir dem Format todo-i
folgen, wobei i
jedes Mal um eins größer wird. Aktualisieren Sie die Todo
-Komponenteninstanzen in App.jsx
, um id
-Props wie folgt hinzuzufügen:
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
<Todo name="Eat" id="todo-0" completed />
<Todo name="Sleep" id="todo-1" />
<Todo name="Repeat" id="todo-2" />
</ul>
Hinweis:
Die completed
-Prop steht hier zuletzt, weil sie ein boolescher Wert ohne Zuweisung ist. Dies ist rein eine stilistische Konvention. Die Reihenfolge der Props spielt keine Rolle, da Props JavaScript-Objekte sind und JavaScript-Objekte ungeordnet sind.
Gehen Sie nun zurück zu Todo.jsx
und verwenden Sie die id
-Prop. Sie muss den Wert des id
-Attributs auf dem <input />
-Element sowie des htmlFor
-Attributs auf seinem <label>
ersetzen:
<div className="c-cb">
<input id={props.id} type="checkbox" defaultChecked={props.completed} />
<label className="todo-label" htmlFor={props.id}>
{props.name}
</label>
</div>
Mit diesen Korrekturen an Ort und Stelle wird das Klicken auf die Labels neben den Kontrollkästchen das tun, was wir erwarten – die Kontrollkästchen neben diesen Labels ein- und ausschalten.
Bis jetzt, alles gut?
Wir nutzen React bisher gut, aber wir könnten es besser machen! Unser Code ist repetitiv. Die drei Zeilen, die unsere <Todo />
-Komponente rendern, sind fast identisch, mit nur einem Unterschied: dem Wert jeder Prop.
Wir können unseren Code mit einer der Kernfähigkeiten von JavaScript bereinigen: Iteration. Um Iteration zu verwenden, sollten wir unsere Aufgaben zunächst neu überdenken.
Aufgaben als Daten
Jede unserer Aufgaben enthält derzeit drei Informationsstücke: ihren Namen, ob sie angekreuzt ist, und ihre eindeutige ID. Diese Daten lassen sich gut in ein Objekt übersetzen. Da wir mehr als eine Aufgabe haben, würde ein Array von Objekten gut zur Darstellung dieser Daten funktionieren.
Deklarieren Sie in src/main.jsx
eine neue const
unter dem letzten Import, aber über ReactDOM.createRoot()
:
const DATA = [
{ id: "todo-0", name: "Eat", completed: true },
{ id: "todo-1", name: "Sleep", completed: false },
{ id: "todo-2", name: "Repeat", completed: false },
];
Hinweis:
Wenn Ihr Texteditor ein ESLint-Plugin hat, sehen Sie möglicherweise eine Warnung über diese DATA
-Konstante. Diese Warnung kommt von der ESLint-Konfiguration, die von der Vite-Vorlage bereitgestellt wird, die wir verwendet haben, und sie gilt nicht für diesen Code. Sie können die Warnung sicher unterdrücken, indem Sie // eslint-disable-next-line
in die Zeile über der DATA
-Konstante einfügen.
Als nächstes werden wir DATA
als eine Prop namens tasks
an <App />
übergeben. Aktualisieren Sie Ihren <App />
-Komponentenaufruf innerhalb von src/main.jsx
, um so auszusehen:
<App tasks={DATA} />
Das DATA
-Array ist jetzt innerhalb der App-Komponente als props.tasks
verfügbar. Sie können console.log()
verwenden, um es zu überprüfen, wenn Sie möchten.
Hinweis:>ALL_CAPS
Konstante Namen haben keine spezielle Bedeutung in JavaScript; sie sind eine Konvention, die anderen Entwicklern sagt: "Diese Daten werden sich nie ändern, nachdem sie hier definiert wurden".
Rendering mit Iteration
Um unser Array von Objekten zu rendern, müssen wir jedes Objekt in eine <Todo />
-Komponente umwandeln. JavaScript bietet uns eine Array-Methode, um Elemente in etwas anderes zu transformieren: Array.prototype.map()
.
Erstellen Sie innerhalb von App.jsx
eine neue const
oberhalb der return
-Anweisung der App()
-Funktion namens taskList
. Lassen Sie uns damit beginnen, jede Aufgabe im props.tasks
-Array in ihren name
zu verwandeln. Der ?.
-Operator ermöglicht uns optionales Chaining, um zu überprüfen, ob props.tasks
undefined
oder null
ist, bevor wir versuchen, ein neues Array mit Aufgabennamen zu erstellen:
const taskList = props.tasks?.map((task) => task.name);
Lassen Sie uns versuchen, alle Kinder des <ul>
durch taskList
zu ersetzen:
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
{taskList}
</ul>
Das bringt uns einen Teil des Weges dazu, alle Komponenten wieder anzuzeigen, aber wir haben noch mehr Arbeit zu tun: Der Browser rendert derzeit jeden Aufgabennamen als reinen Text. Uns fehlt unsere HTML-Struktur — das <li>
und seine Kontrollkästchen und Schaltflächen!
Um dies zu beheben, müssen wir eine <Todo />
-Komponente aus unserer map()
-Funktion zurückgeben — denken Sie daran, dass JSX JavaScript ist, also können wir es neben jeder anderen, vertrauteren JavaScript-Syntax verwenden. Lassen Sie uns Folgendes anstelle dessen versuchen, was wir bereits haben:
const taskList = props.tasks?.map((task) => <Todo />);
Sehen Sie sich Ihre App noch einmal an; jetzt sehen unsere Aufgaben mehr aus wie vorher, aber ihnen fehlen die Namen der Aufgaben selbst. Denken Sie daran, dass jede Aufgabe, über die wir iterieren, die id
, name
und completed
Eigenschaften enthält, die wir in unserer <Todo />
-Komponente übergeben möchten. Wenn wir dieses Wissen zusammenfügen, erhalten wir einen Code wie diesen:
const taskList = props.tasks?.map((task) => (
<Todo id={task.id} name={task.name} completed={task.completed} />
));
Jetzt sieht die App aus wie zuvor, und unser Code ist weniger repetitiv.
Eindeutige Schlüssel
Jetzt, da React unsere Aufgaben aus einem Array rendert, muss es den Überblick darüber behalten, welche welche ist, um sie richtig zu rendern. React versucht, seine eigenen Vermutungen anzustellen, um den Überblick zu behalten, aber wir können es unterstützen, indem wir unserer <Todo />
-Komponente einen key
-Prop übergeben. key
ist ein spezieller Prop, der von React verwaltet wird – Sie dürfen das Wort key
nicht für andere Zwecke verwenden.
Da Schlüssel eindeutig sein sollten, werden wir die id
jedes Aufgabenobjekts als seinen Schlüssel wiederverwenden. Aktualisieren Sie Ihre taskList
-Konstante so:
const taskList = props.tasks?.map((task) => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
/>
));
Sie sollten immer einen eindeutigen Schlüssel an alles übergeben, was Sie mit Iteration rendern. Im Browser wird sich nichts Offensichtliches ändern, aber wenn Sie keine eindeutigen Schlüssel verwenden, wird React Warnungen in Ihre Konsole protokollieren, und Ihre App kann sich seltsam verhalten!
Komponentisierung des Restes der App
Jetzt, wo wir unsere wichtigste Komponente geklärt haben, können wir den Rest unserer App in Komponenten verwandeln. Wenn wir bedenken, dass Komponenten entweder offensichtliche Teile der Benutzeroberfläche, wiederverwendete Teile der Benutzeroberfläche oder beides sind, können wir zwei weitere Komponenten erstellen:
<Form />
<FilterButton />
Da wir wissen, dass wir beides benötigen, können wir einige der Dateierstellungen mit einem Befehl im Terminal zusammenfassen. Führen Sie diesen Befehl in Ihrem Terminal aus und achten Sie darauf, dass Sie sich im Stammverzeichnis Ihrer App befinden:
touch src/components/{Form,FilterButton}.jsx
Der <Form />
Öffnen Sie components/Form.jsx
und führen Sie Folgendes aus:
- Deklarieren Sie eine
Form()
-Funktion und exportieren Sie sie am Ende der Datei. - Kopieren Sie die
<form>
-Tags und alles dazwischen ausApp.jsx
und fügen Sie diese in diereturn
-Anweisung vonForm()
ein.
Ihre Form.jsx
-Datei sollte folgendermaßen aussehen:
function Form() {
return (
<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>
);
}
export default Form;
Der <FilterButton />
Führen Sie dieselben Schritte, die Sie für das Erstellen von Form.jsx
durchgeführt haben, in FilterButton.jsx
aus, aber benennen Sie die Komponente FilterButton()
und kopieren Sie das HTML des ersten Buttons innerhalb <div className="filters btn-group stack-exception">
von App.jsx
in die return
-Anweisung ein.
Die Datei sollte folgendermaßen aussehen:
function FilterButton() {
return (
<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>
);
}
export default FilterButton;
Hinweis:
Sie bemerken vielleicht, dass wir hier denselben Fehler machen wie zunächst bei der <Todo />
-Komponente, indem wir jeden Button gleich machen. Das ist in Ordnung! Wir werden diese Komponente später in Zurück zu den Filter-Schaltflächen beheben.
Importieren aller unserer Komponenten
Lassen Sie uns unsere neuen Komponenten verwenden. Fügen Sie einige weitere import
-Anweisungen oben in App.jsx
hinzu und referenzieren Sie die Komponenten, die wir gerade erstellt haben. Aktualisieren Sie dann die return
-Anweisung von App()
, sodass sie unsere Komponenten rendert.
Wenn Sie fertig sind, wird App.jsx
so aussehen:
import Form from "./components/Form";
import FilterButton from "./components/FilterButton";
import Todo from "./components/Todo";
function App(props) {
const taskList = props.tasks?.map((task) => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
/>
));
return (
<div className="todoapp stack-large">
<h1>TodoMatic</h1>
<Form />
<div className="filters btn-group stack-exception">
<FilterButton />
<FilterButton />
<FilterButton />
</div>
<h2 id="list-heading">3 tasks remaining</h2>
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
{taskList}
</ul>
</div>
);
}
export default App;
Mit diesem Einstellung sollte Ihre React-App im Wesentlichen so rendern, wie sie es zuvor getan hat, aber unter Verwendung Ihrer neuen Komponenten.