Komponenten in unserer React-App erstellen
Zum jetzigen Zeitpunkt ist unsere App ein Monolith. Bevor wir sie in Aktion bringen können, müssen wir sie in handhabbare, beschreibende Komponenten zerlegen. React hat keine festen Regeln dafür, was eine Komponente ist und was nicht – das bleibt Ihnen überlassen! In diesem Artikel zeigen wir Ihnen eine sinnvolle Möglichkeit, unsere App in Komponenten zu zerlegen.
Voraussetzungen: | Vertrautheit mit den Kernsprachen HTML, CSS und JavaScript sowie aus der Terminal/Kommandzeile. |
---|---|
Lernziele: | Eine sinnvolle Möglichkeit, unsere To-Do-Liste-App in Komponenten aufzuteilen. |
Unsere erste Komponente definieren
Eine Komponente zu definieren, kann knifflig erscheinen, bis man etwas Übung hat, aber im Wesentlichen gilt:
- Wenn es ein offensichtliches "Stück" Ihrer App darstellt, ist es wahrscheinlich eine Komponente.
- Wenn es häufig wiederverwendet wird, ist es wahrscheinlich eine Komponente.
Dieser zweite Punkt ist besonders wertvoll: Durch das Erstellen von Komponenten aus gemeinsamen UI-Elementen können Sie Ihren Code an einer Stelle ändern und diese Änderungen überall sehen, wo diese Komponente verwendet wird. Sie müssen nicht alles sofort in Komponenten zerlegen. Lassen Sie uns den zweiten Punkt als Inspirationsquelle nehmen und eine Komponente aus dem am häufigsten verwendeten, wichtigsten Teil der Benutzeroberfläche machen: einem To-Do-Listenelement.
Erstellen Sie 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 angehalten 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 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 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;
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 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 Unvorteilhaftes bemerken: Ihre Liste wiederholt jetzt dreimal die erste Aufgabe!
Wir wollen nicht nur essen; wir haben noch andere Dinge zu tun – nun ja – zu erledigen. Als nächstes werden wir untersuchen, wie wir verschiedene Komponentenaufrufe einzigartigen Inhalt rendern lassen können.
Ein einzigartiges <Todo />
erstellen
Komponenten sind mächtig, weil sie es uns ermöglichen, Teile unserer Benutzeroberfläche wiederzuverwenden und auf eine Quelle für diese Benutzeroberfläche zu verweisen. Das Problem ist, dass wir in der Regel nicht das gesamte einer Komponente wiederverwenden möchten; wir wollen die meisten Teile wiederverwenden und kleine Stücke ändern. Hier kommen "props" ins Spiel.
Was steckt in einem name
?
Um die Namen der Aufgaben, die wir erledigen wollen, nachzuverfolgen, sollten wir sicherstellen, dass jede <Todo />
Komponente einen eindeutigen Namen rendert.
Geben Sie in App.jsx
jeder <Todo />
-Komponente ein Namen-Prop. Verwenden Sie die Namen unserer vorherigen Aufgaben:
<Todo name="Eat" />
<Todo name="Sleep" />
<Todo name="Repeat" />
Wenn Ihr Browser aktualisiert wird, sehen Sie... genau dasselbe wie vorher. Wir haben unseren <Todo />
-Komponenten einige Props gegeben, nutzen sie jedoch noch nicht. Lassen Sie uns zu Todo.jsx
zurückkehren und das beheben.
Ändern Sie zuerst Ihre Todo()
-Funktionsdefinition so, dass sie props
als Parameter annimmt. Sie können console.log()
Ihre Props verwenden, um zu überprüfen, ob sie korrekt von der Komponente empfangen werden.
Sobald Sie sich sicher sind, dass Ihre Komponente ihre Props erhält, können Sie jede Instanz von Eat
durch Ihr 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.
Insgesamt sollte Ihre Todo()
-Funktion nun folgendermaßen 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 immer noch standardmäßig markiert.
Ist es completed
?
In unserer ursprünglichen statischen Liste war nur Eat
markiert. Wieder möchten wir den größten Teil der Benutzeroberfläche, die eine <Todo />
-Komponente ausmacht, wiederverwenden, aber eine Sache ändern. Das ist eine gute Aufgabe 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 nutzen. Ändern Sie das defaultChecked
-Attribut im <input />
, damit dessen Wert dem completed
Prop entspricht. Wenn Sie fertig sind, wird das <input />
-Element der Todo-Komponente folgendermaßen aussehen:
<input id="todo-0" type="checkbox" defaultChecked={props.completed} />
Und Ihr Browser sollte aktualisiert werden, um nur Eat
als markiert anzuzeigen:
Wenn Sie das completed
Prop jeder <Todo />
-Komponente ändern, werden die entsprechenden gerenderten Kontrollkästchen im Browser entsprechend markiert oder entmarkiert.
Geben Sie mir bitte id
Wir haben immer noch ein weiteres 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, von CSS, JavaScript usw. verwendet).- Wenn
id
s nicht eindeutig sind, kann die Funktionalität von Label-Elementen beeinträchtigt werden.
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 bestimmten id
-Attribut an, was das bei den anderen Labels beobachtete Problem verursacht.
Wir hatten eindeutige id
-Attribute, bevor wir die <Todo />
-Komponente erstellt haben. Lassen Sie uns sie zurückbringen, indem wir das Format von todo-i
folgen, wobei i
jedes Mal um eins größer wird. Aktualisieren Sie die Todo
-Komponenteninstanzen in App.jsx
, um id
-Props hinzuzufügen:
<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 Boolean ohne Zuweisung ist. Dies ist rein eine stilistische Konvention. Die Reihenfolge der Props spielt keine Rolle, da Props JavaScript-Objekte sind und JavaScript-Objekte keine geordnete Reihenfolge haben.
Gehen Sie jetzt zurück zu Todo.jsx
und machen Sie Gebrauch vom id
Prop. Es muss den Wert des id
-Attributs des <input />
-Elements sowie den Wert des htmlFor
-Attributs des <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 wird das Anklicken der Labels neben jedem Kontrollkästchen das tun, was wir erwarten – die Kontrollkästchen neben diesen Labels ein- und ausschalten.
Bisher, so 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 jedes Props.
Wir können unseren Code mit einer der Kernfähigkeiten von JavaScript bereinigen: Iteration. Um Iteration zu verwenden, müssen wir zunächst über unsere Aufgaben nachdenken.
Aufgaben als Daten
Jede unserer Aufgaben enthält derzeit drei Informationen: ihren Namen, ob sie markiert wurde und ihre eindeutige ID. Diese Daten lassen sich gut in ein Objekt übertragen. Da wir mehr als eine Aufgabe haben, würde sich ein Array von Objekten gut zur Darstellung dieser Daten eignen.
Deklarieren Sie in src/main.jsx
eine neue const
unterhalb des letzten Imports, aber oberhalb von 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 zu dieser DATA
-Konstanten. Diese Warnung stammt aus der mit dem Vite-Template gelieferten ESLint-Konfiguration, und sie gilt nicht für diesen Code. Sie können die Warnung sicher unterdrücken, indem Sie // eslint-disable-next-line
oberhalb der DATA
-Konstante hinzufügen.
Als Nächstes übergeben wir DATA
als Prop mit dem Namen tasks
an <App />
. Aktualisieren Sie den <App />
-Komponentenaufruf in src/main.jsx
, sodass er wie folgt aussieht:
<App tasks={DATA} />
Das DATA
-Array ist nun in 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
const-Namen haben in JavaScript keine spezielle Bedeutung; sie sind eine Konvention, die anderen Entwicklern mitteilt, dass "diese Daten, sobald sie hier definiert sind, sich nie wieder ändern werden".
Rendering mit Iteration
Um unser Array von Objekten zu rendern, müssen wir jedes Objekt in eine <Todo />
-Komponente umwandeln. JavaScript stellt uns eine Array-Methode zur Verfügung, um Elemente in etwas anderes zu transformieren: Array.prototype.map()
.
Erstellen Sie in App.jsx
über der return
-Anweisung der App()
-Funktion eine neue const
namens taskList
. Beginnen wir damit, jede Aufgabe im props.tasks
-Array in ihren Namen zu verwandeln. Der ?.
-Operator ermöglicht es uns, eine optionale Verkettung durchzuführen, 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);
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 weit dahin, alle Komponenten wieder anzuzeigen, aber wir haben noch mehr Arbeit zu erledigen: Der Browser zeigt derzeit den Namen jeder Aufgabe als einfachen Text an. Uns fehlt die HTML-Struktur – das <li>
und seine Kontrollkästchen und Schaltflächen!
Um dies zu beheben, müssen wir aus unserer map()
Funktion eine <Todo />
Komponente zurückgeben – denken Sie daran, dass JSX JavaScript ist, sodass wir es neben jeder anderen, bekannteren 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 abbilden, die id
, name
und completed
Eigenschaften enthält, die wir in unsere <Todo />
Komponente einfügen 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 nun aus einem Array rendert, muss es verfolgen, welche Aufgabe welche ist, um sie korrekt rendern zu können. React versucht, seine eigene Schätzung zu machen, um Dinge zu verfolgen, aber wir können ihm helfen, indem wir einen key
Prop an unsere <Todo />
Komponenten übergeben. key
ist ein spezieller Prop, der von React verwaltet wird – Sie können das Wort key
nicht für andere Zwecke verwenden.
Da Schlüssel eindeutig sein sollten, werden wir die id
-Eigenschaft 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}
/>
));
Zu jedem Element, das Sie mit Iteration rendern, sollten Sie immer einen eindeutigen Schlüssel übergeben. Nichts Offensichtliches wird sich in Ihrem Browser ändern, aber wenn Sie keine eindeutigen Schlüssel verwenden, wird React Warnungen in Ihre Konsole protokollieren und Ihre App kann sich seltsam verhalten!
Den Rest der App in Komponenten umwandeln
Jetzt, da wir unsere wichtigste Komponente sortiert haben, können wir den Rest unserer App in Komponenten umwandeln. Wenn wir uns daran erinnern, 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 einen Teil der Dateierstellungsarbeit zusammen in einem Terminalbefehl ausführen. 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
Die <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 zwischen ihnen ausApp.jsx
und fügen Sie sie in diereturn
Anweisung vonForm()
ein.
Ihre Form.jsx
Datei sollte nun wie folgt 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 in FilterButton.jsx
die gleichen Schritte aus, die Sie zum Erstellen von Form.jsx
ausgeführt haben, aber benennen Sie die Komponente FilterButton()
und kopieren Sie das HTML für die erste Schaltfläche innerhalb von <div className="filters btn-group stack-exception">
aus App.jsx
in die return
Anweisung.
Die Datei sollte wie folgt 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 für die <Todo />
-Komponente gemacht haben, nämlich dass jede Schaltfläche gleich sein wird. Das ist in Ordnung! Wir werden diese Komponente später beheben, in Zurück zu den Filter-Schaltflächen.
Alle unsere Komponenten importieren
Lassen Sie uns von unseren neuen Komponenten profitieren. Fügen Sie weitere import
Anweisungen am Anfang von App.jsx
hinzu und referenzieren Sie die Komponenten, die wir gerade erstellt haben. Aktualisieren Sie dann die return
Anweisung von App()
, damit sie unsere Komponenten rendert.
Wenn Sie fertig sind, wird App.jsx
wie folgt 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 all dem sollte Ihre React-App im Grunde so angezeigt werden wie zuvor, aber unter Verwendung Ihrer neuen, glänzenden Komponenten.