Komponentisieren unserer React-App
An diesem Punkt ist unsere App ein Monolith. Bevor wir sie funktional gestalten können, müssen wir sie in handhabbare, beschreibende Komponenten aufteilen. React hat keine strengen 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 grundlegenden HTML, CSS und JavaScript Sprachen, Kenntnisse über die Terminal/Befehlszeile. |
---|---|
Ziel: | Eine sinnvolle Methode zeigen, wie unsere To-Do-Listen-App in Komponenten aufgeteilt werden kann. |
Definieren unserer ersten Komponente
Das Definieren einer Komponente kann knifflig erscheinen, bis Sie ein wenig Übung darin haben, aber das Wesentliche ist:
- Wenn es einen offensichtlichen "Block" Ihrer App darstellt, ist es wahrscheinlich eine Komponente.
- Wenn es oft wiederverwendet wird, ist es wahrscheinlich eine Komponente.
Dieser zweite Punkt ist besonders wertvoll: Aus häufig verwendeten UI-Elementen eine Komponente zu machen, ermöglicht es Ihnen, Ihren Code an einem Ort zu ändern und diese Änderungen überall dort zu sehen, wo diese Komponente verwendet wird. Sie müssen nicht sofort alles in Komponenten aufteilen. Lassen Sie uns den zweiten Punkt als Inspiration nehmen und eine Komponente aus dem am häufigsten verwendeten, wichtigsten Teil der Benutzeroberfläche erstellen: einem To-Do-Listen-Eintrag.
Erstellen eines <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 Root-Verzeichnis 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!
Fügen Sie eine Todo()
Funktion in Todo.jsx
hinzu. Hier definieren wir eine Funktion und exportieren sie:
function Todo() {}
export default Todo;
Das ist bisher 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 so 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;
Jetzt haben wir etwas, das wir verwenden können. Fügen Sie in App.jsx
die folgende Zeile am Anfang der Datei hinzu, um Todo
zu importieren:
import Todo from "./components/Todo";
Mit dieser Komponente importiert, können Sie alle <li>
-Elemente in App.jsx
durch <Todo />
-Komponentenaufrufe ersetzen. Ihr <ul>
sollte so 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 die erste Aufgabe dreimal!
Wir wollen nicht nur essen; wir haben noch andere Dinge zu tun. Als nächstes untersuchen wir, wie wir verschiedene Komponentenaufrufe dazu bringen können, einzigartigen Inhalt anzuzeigen.
Erstellen eines einzigartigen <Todo />
Komponenten sind mächtig, weil sie es uns ermöglichen, Teile unserer Benutzeroberfläche wiederzuverwenden und auf eine Quelle dieser Benutzeroberfläche zu verweisen. Das Problem ist, dass wir normalerweise nicht alles in jeder Komponente wiederverwenden wollen; wir möchten die meisten Teile und kleine Teile ändern. Hier kommen Props ins Spiel.
Was ist in einem name
?
Um die Namen der Aufgaben zu verfolgen, die wir erledigen möchten, sollten wir sicherstellen, dass jede <Todo />
-Komponente einen einzigartigen Namen rendert.
Geben Sie in App.jsx
jeder <Todo />
einen Namens-Prop. Verwenden wir die Namen unserer vorherigen Aufgaben:
<Todo name="Eat" />
<Todo name="Sleep" />
<Todo name="Repeat" />
Wenn Ihr Browser aktualisiert wird, sehen Sie... genau das Gleiche wie zuvor. Wir haben unseren <Todo />
-Komponenten einige Props gegeben, aber wir verwenden sie noch nicht. Gehen wir zurück zu Todo.jsx
und beheben das.
Modifizieren Sie zunächst die Definition Ihrer Todo()
-Funktion, sodass sie props
als Parameter nimmt. Sie können console.log()
Ihre Props verwenden, wenn Sie überprüfen möchten, ob sie von der Komponente korrekt empfangen werden.
Sobald Sie sicher sind, dass Ihre Komponente ihre Props erhält, können Sie jede Vorkommen von Eat
durch Ihren name
-Prop ersetzen, indem Sie props.name
lesen. Denken Sie daran: props.name
ist ein JSX-Ausdruck, also müssen Sie ihn in geschweifte Klammern einschließen.
Wenn Sie alles zusammenfügen, 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 einzigartige Aufgaben anzeigen. Ein weiteres Problem bleibt jedoch: Sie sind alle standardmäßig aktiviert.
Ist es completed
?
In unserer ursprünglichen statischen Liste war nur Eat
markiert. Wieder einmal wollen wir meistens die UI, die eine <Todo />
-Komponente ausmacht, wiederverwenden, aber eine Sache ändern. Das ist ein guter Job für ein weiteres Prop! Geben Sie Ihrem ersten <Todo />
-Aufruf ein boolesches completed
-Prop, und lassen Sie die anderen beiden so wie sie sind.
<Todo name="Eat" completed />
<Todo name="Sleep" />
<Todo name="Repeat" />
Wie zuvor, müssen wir zu Todo.jsx
zurückkehren, um diese Props tatsächlich zu verwenden. Ändern Sie das defaultChecked
-Attribut auf dem <input />
, sodass sein Wert gleich dem completed
-Prop ist. Sobald Sie fertig sind, wird das <input />
-Element der Todo
-Komponente so aussehen:
<input id="todo-0" type="checkbox" defaultChecked={props.completed} />
Und Ihr Browser sollte so aktualisiert werden, dass nur Eat
markiert ist:
Wenn Sie das completed
-Prop jeder <Todo />
-Komponente ändern, wird Ihr Browser die entsprechenden gerenderten Kontrollkästchen entsprechend aktivieren oder deaktivieren.
Geben Sie mir etwas id
, bitte
Wir haben noch ein weiteres Problem: Unsere <Todo />
-Komponente gibt jeder Aufgabe ein id
-Attribut von todo-0
. Das ist aus mehreren Gründen schlecht:
id
-Attribute müssen eindeutig sein (sie werden als eindeutige Kennungen für Dokumentfragmente verwendet, von CSS, JavaScript, etc.).- Wenn
id
s nicht eindeutig sind, kann die Funktionalität von Label-Elementen fehlerhaft sein.
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 jedes <label>
-Element der Kontrollkästchen ein htmlFor
-Attribut von todo-0
hat. Die <label>
s erkennen nur das erste Element mit einem gegebenen id
-Attribut an, was das Problem verursacht, das Sie beim Klicken auf die anderen Labels sehen.
Vor der Erstellung der <Todo />
-Komponente hatten wir eindeutige id
-Attribute. Lassen Sie uns sie zurückbringen und der Formatierung todo-i
folgen, wobei i
jedes Mal um eins erhöht wird. Aktualisieren Sie die Todo
-Komponenteninstanzen in App.jsx
, um id
-Props hinzuzufügen, wie folgt:
<Todo name="Eat" id="todo-0" completed />
<Todo name="Sleep" id="todo-1" />
<Todo name="Repeat" id="todo-2" />
Hinweis:
Das completed
-Prop steht hier zuletzt, weil es ein Boolescher ist und keine Zuweisung hat. 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 nutzen Sie das id
-Prop. Es muss den Wert des id
-Attributs im <input />
-Element sowie den Wert des htmlFor
-Attributs im <label>
-Element 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 wird das Klicken auf die Labels neben jedem Kontrollkästchen das tun, was wir erwarten – die Kontrollkästchen neben diesen Labels aktivieren und deaktivieren.
Bisher, so gut?
Bisher nutzen wir React 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 jedes Props.
Wir können unseren Code mit einer der Kernfähigkeiten von JavaScript aufräumen: der Iteration. Um Iteration zu verwenden, sollten wir unsere Aufgaben neu überdenken.
Aufgaben als Daten
Jede unserer Aufgaben enthält derzeit drei Informationen: ihren Namen, ob sie aktiviert ist, und ihre eindeutige ID. Diese Daten lassen sich gut in ein Objekt übersetzen. Da wir mehr als eine Aufgabe haben, wäre ein Array von Objekten gut geeignet, um diese Daten darzustellen.
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, könnten Sie eine Warnung über diese DATA
-Konstante sehen. Diese Warnung stammt von der ESLint-Konfiguration, die durch das von uns verwendete Vite-Template geliefert wird, und sie gilt nicht für diesen Code. Sie können die Warnung sicher unterdrücken, indem Sie // eslint-disable-next-line
über der DATA
-Konstante hinzufügen.
Als nächstes übergeben wir DATA
an <App />
als ein Prop namens tasks
. Aktualisieren Sie Ihren <App />
-Komponentenaufruf in src/main.jsx
, sodass er so aussieht:
<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: Konstantennamen in ALL_CAPS
haben keine besondere Bedeutung in JavaScript; sie sind eine Konvention, die anderen Entwicklern sagt "diese Daten werden sich hier nach der Definition nicht ändern".
Rendering mit Iteration
Um unser Array von Objekten zu rendern, müssen wir jedes Objekt in eine <Todo />
-Komponente umwandeln. JavaScript gibt uns eine Array-Methode, um Elemente in etwas anderes zu transformieren: Array.prototype.map()
.
Erstellen Sie in App.jsx
eine neue const
über der return
-Anweisung der App()
-Funktion, die taskList
genannt wird. Beginnen wir damit, jede Aufgabe im props.tasks
-Array in ihren name
zu transformieren. Der ?.
-Operator erlaubt uns, eine optionale Verkettung durchzuführen, um zu überprüfen, ob props.tasks
undefined
oder null
ist, bevor Sie versuchen, ein neues Array von Aufgabennamen zu erstellen:
const taskList = props.tasks?.map((task) => task.name);
Versuchen wir, alle Kinder des <ul>
mit taskList
zu ersetzen:
<ul
role="list"
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
{taskList}
</ul>
Dies bringt uns ein Stück weiter dahin, alle Komponenten wieder anzuzeigen, aber wir haben noch mehr zu tun: Der Browser rendert derzeit jeden Aufgabennamen als normalen Text. Uns fehlt unsere HTML-Struktur – die <li>
und ihre 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, sodass wir es zusammen mit jeder anderen, vertrauteren JavaScript-Syntax verwenden können. Versuchen wir das Folgende anstelle dessen, was wir bereits haben:
const taskList = props.tasks?.map((task) => <Todo />);
Schauen Sie sich Ihre App noch einmal an; jetzt sehen unsere Aufgaben mehr aus wie früher, aber ihnen fehlen die Namen der Aufgaben selbst. Denken Sie daran, dass jede Aufgabe, die wir durchlaufen, die id
, name
und completed
-Eigenschaften enthält, die wir an unsere <Todo />
-Komponente übergeben möchten. Wenn wir dieses Wissen zusammenfügen, erhalten wir 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.
Einzigartige Schlüssel
Da React unsere Aufgaben jetzt aus einem Array rendert, muss es den Überblick behalten, welche welche ist, um sie korrekt darzustellen. React versucht, seine eigene Vermutung zu machen, um den Überblick zu behalten, aber wir können ihm helfen, indem wir ein key
-Prop an unsere <Todo />
-Komponenten übergeben. key
ist ein spezielles Prop, das von React verwaltet wird – Sie können das Wort key
für keinen anderen Zweck verwenden.
Da Schlüssel einzigartig sein sollten, werden wir die id
jedes Aufgabenobjekts als seinen Schlüssel wiederverwenden. Aktualisieren Sie Ihre taskList
-Konstante wie folgt:
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. In Ihrem 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!
Komponentisieren des restlichen Teils der App
Da wir unsere wichtigste Komponente organisiert haben, können wir den Rest unserer App in Komponenten umwandeln. Erinnert man sich daran, 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 beide benötigen, können wir einige der Dateierstellungsarbeiten zusammen in einem Terminalbefehl erledigen. Führen Sie diesen Befehl in Ihrem Terminal aus und achten Sie darauf, dass Sie sich im Root-Verzeichnis Ihrer App befinden:
touch src/components/{Form,FilterButton}.jsx
Das <Form />
Öffnen Sie components/Form.jsx
und führen Sie die folgenden Schritte 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 sie in diereturn
-Anweisung vonForm()
ein.
Ihre Datei Form.jsx
sollte so 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 />
Machen Sie dasselbe, was Sie getan haben, um Form.jsx
in FilterButton.jsx
zu erstellen, aber nennen Sie die Komponente FilterButton()
und kopieren Sie das HTML für die erste Schaltfläche innerhalb der <div className="filters btn-group stack-exception">
aus App.jsx
in die return
-Anweisung.
Die Datei sollte so 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:
Möglicherweise bemerken Sie, dass wir hier denselben Fehler machen, den wir zuerst bei der <Todo />
-Komponente gemacht haben, nämlich dass jede Schaltfläche gleich sein wird. Das ist in Ordnung! Wir werden diese Komponente später reparieren, in Zurück zu den Filter-Schaltflächen.
Importieren all unserer Komponenten
Lassen Sie uns unsere neuen Komponenten verwenden. Fügen Sie weitere import
-Anweisungen oben in App.jsx
hinzu und referenzieren Sie die Komponenten, die wir gerade erstellt haben. Dann aktualisieren Sie 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;
Damit sollte Ihre React-App im Grunde genauso gerendert werden wie zuvor, aber mit Ihren glänzenden neuen Komponenten.