Komponentisierung unserer Svelte-App
Im letzten Artikel haben wir begonnen, unsere To-do-Listen-App zu entwickeln. Das Hauptziel dieses Artikels ist es zu untersuchen, wie wir unsere App in handhabbare Komponenten aufteilen und Informationen zwischen ihnen teilen können. Wir werden unsere App komponentisieren und dann mehr Funktionalität hinzufügen, um Benutzern das Aktualisieren vorhandener Komponenten zu ermöglichen.
Voraussetzungen: |
Es wird mindestens empfohlen, dass Sie mit den grundlegenden HTML, CSS und JavaScript-Sprachen vertraut sind und Kenntnisse über das Terminal/Befehlszeile haben. Sie benötigen ein Terminal mit installierten Node und npm, um Ihre App zu kompilieren und zu bauen. |
---|---|
Ziel: | Lernen, wie man unsere App in Komponenten aufteilt und Informationen unter ihnen teilt. |
Code mit uns mit
Git
Klonen Sie das GitHub-Repo (falls Sie es noch nicht getan haben) mit:
git clone https://github.com/opensas/mdn-svelte-tutorial.git
Um den aktuellen App-Status zu erreichen, führen Sie aus
cd mdn-svelte-tutorial/04-componentizing-our-app
Oder laden Sie den Inhalt des Ordners direkt herunter:
npx degit opensas/mdn-svelte-tutorial/04-componentizing-our-app
Denken Sie daran, npm install && npm run dev
auszuführen, um Ihre App im Entwicklungsmodus zu starten.
REPL
Um den Code mit uns im REPL mitzumachen, starten Sie bei
https://svelte.dev/repl/99b9eb228b404a2f8c8959b22c0a40d3?version=3.23.2
Die App in Komponenten aufteilen
In Svelte besteht eine Anwendung aus einer oder mehreren Komponenten. Eine Komponente ist ein wiederverwendbarer, eigenständiger Codeblock, der zusammengehöriges HTML, CSS und JavaScript kapselt und in einer .svelte
-Datei geschrieben ist. Komponenten können groß oder klein sein, sind aber normalerweise klar definiert: Die effektivsten Komponenten dienen einem einzigen, offensichtlichen Zweck.
Die Vorteile der Definition von Komponenten sind vergleichbar mit der allgemeinen Best Practice, Ihren Code in handhabbare Stücke zu organisieren. Es hilft Ihnen, zu verstehen, wie sie zueinander stehen, fördert die Wiederverwendung und macht Ihren Code leichter nachvollziehbar, wartbar und erweiterbar.
Aber woher wissen Sie, was in eine eigene Komponente aufgeteilt werden sollte?
Es gibt keine festen Regeln dafür. Einige Menschen bevorzugen einen intuitiven Ansatz und beginnen damit, die Markup-Struktur zu betrachten und um jede Komponente und Unterkomponente, die ihre eigene Logik zu haben scheint, Boxen zu ziehen.
Andere Menschen wenden dieselben Techniken an, die zur Entscheidung verwendet werden, ob Sie eine neue Funktion oder ein neues Objekt erstellen sollten. Eine solche Technik ist das Single-Responsibility-Prinzip – das bedeutet, eine Komponente sollte idealerweise nur eine Sache tun. Wenn sie wächst, sollte sie in kleinere Unterkomponenten aufgeteilt werden.
Beide Ansätze sollten sich ergänzen und Ihnen helfen, zu entscheiden, wie Sie Ihre Komponenten besser organisieren können.
Letztendlich werden wir unsere App in die folgenden Komponenten aufteilen:
Alert.svelte
: Eine allgemeine Benachrichtigungsbox für die Kommunikation über durchgeführte Aktionen.NewTodo.svelte
: Das Texteingabefeld und der Button, die Ihnen das Eingeben eines neuen To-do-Elements ermöglichen.FilterButton.svelte
: Die Alle, Aktiv und Abgeschlossen-Buttons, die Ihnen erlauben, Filter auf die angezeigten To-do-Elemente anzuwenden.TodosStatus.svelte
: Die Überschrift "x von y Aufgaben abgeschlossen".Todo.svelte
: Ein einzelnes To-do-Element. Jedes sichtbare To-do-Element wird in einer separaten Kopie dieser Komponente angezeigt.MoreActions.svelte
: Die Buttons Alle auswählen und Abgeschlossene entfernen unten in der Benutzeroberfläche, die Ihnen erlauben, Massenaktionen auf die To-do-Elemente auszuführen.
In diesem Artikel konzentrieren wir uns auf die Erstellung der FilterButton
- und Todo
-Komponenten; die anderen werden wir in zukünftigen Artikeln behandeln.
Lassen Sie uns beginnen.
Hinweis: Während der Erstellung unserer ersten paar Komponenten lernen wir auch verschiedene Techniken zur Kommunikation zwischen Komponenten kennen sowie deren Vor- und Nachteile.
Unsere Filterkomponente extrahieren
Wir beginnen mit der Erstellung unserer FilterButton.svelte
.
-
Erstellen Sie zunächst eine neue Datei,
components/FilterButton.svelte
. -
In dieser Datei deklarieren wir eine
filter
-Eigenschaft und kopieren das relevante Markup vonTodos.svelte
in diese Datei. Fügen Sie den folgenden Inhalt in die Datei ein:svelte<script> export let filter = 'all' </script> <div class="filters btn-group stack-exception"> <button class="btn toggle-btn" class:btn__primary={filter === 'all'} aria-pressed={filter === 'all'} on:click={() => filter = 'all'} > <span class="visually-hidden">Show</span> <span>All</span> <span class="visually-hidden">tasks</span> </button> <button class="btn toggle-btn" class:btn__primary={filter === 'active'} aria-pressed={filter === 'active'} on:click={() => filter = 'active'} > <span class="visually-hidden">Show</span> <span>Active</span> <span class="visually-hidden">tasks</span> </button> <button class="btn toggle-btn" class:btn__primary={filter === 'completed'} aria-pressed={filter === 'completed'} on:click={() => filter = 'completed'} > <span class="visually-hidden">Show</span> <span>Completed</span> <span class="visually-hidden">tasks</span> </button> </div>
-
In unserer
Todos.svelte
-Komponente möchten wir unsereFilterButton
-Komponente verwenden. Zuerst müssen wir sie importieren. Fügen Sie die folgende Zeile oben imTodos.svelte <script>
-Abschnitt hinzu:jsimport FilterButton from "./FilterButton.svelte";
-
Ersetzen Sie nun das
<div class="filters...
-Element durch einen Aufruf derFilterButton
-Komponente, die den aktuellen Filter als Eigenschaft übernimmt. Die folgende Zeile ist alles, was Sie benötigen:svelte<FilterButton {filter} />
Hinweis:
Denken Sie daran, dass wenn der Name des HTML-Attributs und die Variable übereinstimmen, sie durch {variable}
ersetzt werden können. Deshalb konnten wir <FilterButton filter={filter} />
durch <FilterButton {filter} />
ersetzen.
Bis jetzt so gut! Probieren Sie die App jetzt aus. Sie werden feststellen, dass beim Klicken auf die Filterbuttons diese ausgewählt werden und sich der Stil entsprechend aktualisiert. Aber wir haben ein Problem: Die To-dos werden nicht gefiltert. Das liegt daran, dass die filter
-Variable von der Todos
-Komponente zur FilterButton
-Komponente über die Eigenschaft fließt, aber Änderungen, die in der FilterButton
-Komponente auftreten, nicht zu ihrer übergeordneten Komponente zurückfließen – die Datenbindung ist standardmäßig unidirektional. Sehen wir uns eine Möglichkeit an, dies zu lösen.
Daten zwischen Komponenten teilen: Übergabe eines Handlers als Eigenschaft
Eine Möglichkeit, untergeordnete Komponenten ihre übergeordneten Komponenten über Änderungen informieren zu lassen, besteht darin, einen Handler als Eigenschaft zu übergeben. Die untergeordnete Komponente wird den Handler ausführen und die benötigten Informationen als Parameter übergeben, und der Handler wird den Zustand der übergeordneten Komponente ändern.
In unserem Fall wird die FilterButton
-Komponente einen onclick
-Handler von ihrem übergeordneten Element erhalten. Immer wenn der Benutzer auf einen Filterbutton klickt, wird das untergeordnete Element den onclick
-Handler aufrufen und den ausgewählten Filter als Parameter an das übergeordnete Element zurückgeben.
Wir werden einfach die onclick
-Eigenschaft mit einem Dummy-Handler deklarieren, um Fehler zu verhindern, wie folgt:
export let onclick = (clicked) => {};
Und wir deklarieren die reaktive Anweisung $: onclick(filter)
, um den onclick
-Handler aufzurufen, wann immer die filter
-Variable aktualisiert wird.
-
Der
<script>
-Abschnitt unsererFilterButton
-Komponente sollte am Ende so aussehen. Aktualisieren Sie ihn jetzt:jsexport let filter = "all"; export let onclick = (clicked) => {}; $: onclick(filter);
-
Wenn wir nun
FilterButton
inTodos.svelte
aufrufen, müssen wir den Handler angeben. Aktualisieren Sie ihn so:svelte<FilterButton {filter} onclick={ (clicked) => filter = clicked }/>
Wenn ein Filterbutton geklickt wird, aktualisieren wir einfach die Filtervariable mit dem neuen Filter. Jetzt funktioniert unsere FilterButton
-Komponente wieder.
Einfachere bidirektionale Datenbindung mit dem bind-Directive
Im vorherigen Beispiel haben wir erkannt, dass unsere FilterButton
-Komponente nicht funktionierte, weil unser Anwendungszustand von der übergeordneten zur untergeordneten Komponente durch die filter
-Eigenschaft floss, jedoch nicht zurück. Also haben wir eine onclick
-Eigenschaft hinzugefügt, um dem untergeordneten Element zu ermöglichen, den neuen filter
-Wert an das übergeordnete Element zu kommunizieren.
Es funktioniert zwar, aber Svelte bietet uns eine einfachere und direktere Möglichkeit, bidirektionale Datenbindung zu erreichen. Daten fließen in der Regel von übergeordneten zu untergeordneten Komponenten über Eigenschaften. Wenn wir möchten, dass sie auch umgekehrt, also von untergeordnet zu übergeordnet fließen, können wir das bind:
-Directive verwenden.
Indem wir bind
verwenden, sagen wir Svelte, dass alle Änderungen, die an der filter
-Eigenschaft in der FilterButton
-Komponente vorgenommen werden, wieder an die übergeordnete Komponente (Todos
) zurückfließen sollten. Das heißt, wir werden den Wert der filter
-Variablen im übergeordneten Element an ihren Wert im untergeordneten Element binden.
-
Aktualisieren Sie in
Todos.svelte
den Aufruf derFilterButton
-Komponente wie folgt:svelte<FilterButton bind:filter={filter} />
Wie üblich bietet uns Svelte eine praktische Kurzschrift:
bind:value={value}
ist gleichbedeutend mitbind:value
. In dem obigen Beispiel könnten Sie also einfach<FilterButton bind:filter />
schreiben. -
Die untergeordnete Komponente kann jetzt den Wert der Filtervariable des übergeordneten Elements ändern, sodass wir die
onclick
-Eigenschaft nicht mehr benötigen. Ändern Sie das<script>
-Element IhrerFilterButton
-Komponente wie folgt:svelte<script> export let filter = "all"; </script>
-
Probieren Sie Ihre App erneut aus und Sie sollten sehen, dass Ihre Filter weiterhin korrekt funktionieren.
Unsere Todo-Komponente erstellen
Jetzt erstellen wir eine Todo
-Komponente, um jedes einzelne To-do zu kapseln, einschließlich der Checkbox und einigen Bearbeitungslogiken, damit Sie ein bestehendes To-do ändern können.
Unsere Todo
-Komponente erhält ein einzelnes todo
-Objekt als Eigenschaft. Lassen Sie uns die todo
-Eigenschaft deklarieren und den Code von der Todos
-Komponente verschieben. Vorerst ersetzen wir den Aufruf von removeTodo
durch einen Alert. Diese Funktionalität fügen wir später wieder hinzu.
-
Erstellen Sie eine neue Komponentendatei,
components/Todo.svelte
. -
Fügen Sie die folgenden Inhalte in diese Datei ein:
svelte<script> export let todo </script> <div class="stack-small"> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={() => todo.completed = !todo.completed} checked={todo.completed} /> <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn"> Edit <span class="visually-hidden">{todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={() => alert('not implemented')}> Delete <span class="visually-hidden">{todo.name}</span> </button> </div> </div>
-
Nun müssen wir unsere
Todo
-Komponente inTodos.svelte
importieren. Gehen Sie jetzt zu dieser Datei und fügen Sie die folgendeimport
-Anweisung unter Ihrer vorherigen hinzu:jsimport Todo from "./Todo.svelte";
-
Als nächstes müssen wir unseren
{#each}
-Block aktualisieren, um eine<Todo>
-Komponente für jedes To-do einzuschließen, anstatt des Codes, der inTodo.svelte
verschoben wurde. Wir übergeben auch das aktuelletodo
-Objekt als Eigenschaft an die Komponente.Aktualisieren Sie den
{#each}
-Block inTodos.svelte
wie folgt:svelte<ul role="list" class="todo-list stack-large" aria-labelledby="list-heading"> {#each filterTodos(filter, todos) as todo (todo.id)} <li class="todo"> <Todo {todo} /> </li> {:else} <li>Nothing to do here!</li> {/each} </ul>
Die Liste der To-dos wird auf der Seite angezeigt und die Kontrollkästchen sollten funktionieren (versuchen Sie, ein paar zu aktivieren/deaktivieren, und beobachten Sie, dass die Filter weiterhin wie erwartet funktionieren), aber unsere Statusüberschrift "x von y Aufgaben abgeschlossen" wird nicht mehr entsprechend aktualisiert. Das liegt daran, dass unsere Todo
-Komponente das To-do über die Eigenschaft empfängt, aber keine Informationen an ihr übergeordnetes Element zurückschickt. Wir werden dies später beheben.
Daten zwischen Komponenten teilen: Props-Down, Events-Up-Muster
Das bind
-Directive ist sehr direkt und ermöglicht es Ihnen, Daten zwischen einer übergeordneten und einer untergeordneten Komponente mit minimalem Aufwand zu teilen. Wenn Ihre Anwendung jedoch größer und komplexer wird, kann es leicht schwierig werden, den Überblick über alle gebundenen Werte zu behalten. Ein anderer Ansatz ist das "Props-Down, Events-Up"-Kommunikationsmuster.
Grundsätzlich beruht dieses Muster darauf, dass untergeordnete Komponenten Daten von ihren Eltern über Eigenschaften empfangen und übergeordnete Komponenten ihren Zustand aktualisieren, indem sie auf Ereignisse reagieren, die von den untergeordneten Komponenten ausgehen. Also fließen Props von Eltern zu Kind und Ereignisse steigen von Kind zu Eltern auf. Dieses Muster etabliert einen bidirektionalen Informationsfluss, der vorhersehbar und leichter nachvollziehbar ist.
Schauen wir uns an, wie wir unsere eigenen Ereignisse auslösen, um die fehlende Löschen-Button-Funktionalität neu zu implementieren.
Um benutzerdefinierte Ereignisse zu erstellen, verwenden wir das createEventDispatcher
-Utility. Dies wird eine dispatch()
-Funktion zurückgeben, die es uns ermöglicht, benutzerdefinierte Ereignisse auszulösen. Wenn Sie ein Ereignis auslösen, müssen Sie den Namen des Ereignisses und optional ein Objekt mit zusätzlichen Informationen übergeben, das Sie an jeden Listener übergeben möchten. Diese zusätzlichen Daten werden in der detail
-Eigenschaft des Ereignisobjekts verfügbar sein.
Hinweis:
Benutzerdefinierte Ereignisse in Svelte teilen dieselbe API wie reguläre DOM-Ereignisse. Außerdem können Sie ein Ereignis an Ihre übergeordnete Komponente weitergeben, indem Sie on:event
ohne Handler angeben.
Wir werden unsere Todo
-Komponente bearbeiten, um ein remove
-Ereignis auszulösen, wobei das entfernte To-do als zusätzliche Information übergeben wird.
-
Fügen Sie zuerst die folgende Zeile zum Anfang des
<script>
-Abschnitts derTodo
-Komponente hinzu:jsimport { createEventDispatcher } from "svelte"; const dispatch = createEventDispatcher();
-
Aktualisieren Sie nun den Löschen-Button im Markup-Abschnitt derselben Datei, sodass er so aussieht:
svelte<button type="button" class="btn btn__danger" on:click={() => dispatch('remove', todo)}> Delete <span class="visually-hidden">{todo.name}</span> </button>
Mit
dispatch('remove', todo)
lösen wir einremove
-Ereignis aus und übergeben als zusätzliche Daten dastodo
, das gelöscht wird. Der Handler wird mit einem Ereignisobjekt aufgerufen, dessen zusätzliche Daten in derevent.detail
-Eigenschaft verfügbar sind. -
Nun müssen wir diesem Ereignis von innerhalb von
Todos.svelte
aus lauschen und entsprechend handeln. Gehen Sie zurück zu dieser Datei und aktualisieren Sie Ihren<Todo>
-Komponentenaufruf wie folgt:svelte<Todo {todo} on:remove={(e) => removeTodo(e.detail)} />
Unser Handler erhält den Parameter
e
(das Ereignisobjekt), das wie zuvor beschrieben das zu löschende To-do in derdetail
-Eigenschaft enthält. -
An diesem Punkt, wenn Sie Ihre App erneut ausprobieren, sollten Sie sehen, dass die Löschen-Funktionalität jetzt wieder funktioniert. Unser benutzerdefiniertes Ereignis hat also wie erhofft funktioniert. Darüber hinaus sendet der
remove
-Ereignislistener die Datenänderung zurück an den übergeordneten Element, sodass unsere Statusüberschrift "x von y Aufgaben abgeschlossen" jetzt auch dann entsprechend aktualisiert wird, wenn To-dos gelöscht werden.
Nun kümmern wir uns um das update
-Ereignis, damit unsere übergeordnete Komponente über Änderungen an einem To-do informiert werden kann.
Aktualisieren von To-dos
Wir müssen noch die Funktionalität implementieren, die es uns ermöglicht, bestehende To-dos zu bearbeiten. Wir werden einen Bearbeitungsmodus in die Todo
-Komponente integrieren. Im Bearbeitungsmodus zeigen wir ein <input>
-Feld an, das es uns erlaubt, den aktuellen To-do-Namen zu bearbeiten, mit zwei Buttons, um unsere Änderungen zu bestätigen oder abzubrechen.
Die Ereignisse handhaben
-
Wir benötigen eine Variable, um zu verfolgen, ob wir uns im Bearbeitungsmodus befinden, und eine andere, um den Namen der Aufgabe zu speichern, die aktualisiert wird. Fügen Sie die folgenden Variablendefinitionen am Ende des
<script>
-Abschnitts derTodo
-Komponente hinzu:jslet editing = false; // track editing mode let name = todo.name; // hold the name of the to-do being edited
-
Wir müssen entscheiden, welche Ereignisse unsere
Todo
-Komponente auslösen wird:- Wir könnten unterschiedliche Ereignisse für das Umschalten des Status und das Bearbeiten des Namens auslösen (zum Beispiel
updateTodoStatus
undupdateTodoName
). - Oder wir könnten einen allgemeineren Ansatz wählen und ein einzelnes
update
-Ereignis für beide Operationen auslösen.
Wir werden den zweiten Ansatz wählen, um eine andere Technik zu demonstrieren. Der Vorteil dieses Ansatzes ist, dass wir später mehr Felder zu den To-dos hinzufügen können und dennoch alle Updates mit demselben Ereignis abwickeln.
Lassen Sie uns eine
update()
-Funktion erstellen, die die Änderungen empfängt und ein Update-Ereignis mit dem modifizierten To-do auslöst. Fügen Sie das folgende, erneut am Ende des<script>
-Abschnitts hinzu:jsfunction update(updatedTodo) { todo = { ...todo, ...updatedTodo }; // applies modifications to todo dispatch("update", todo); // emit update event }
Hier verwenden wir die Spread-Syntax, um das originale To-do mit den darauf angewendeten Änderungen zurückzugeben.
- Wir könnten unterschiedliche Ereignisse für das Umschalten des Status und das Bearbeiten des Namens auslösen (zum Beispiel
-
Als nächstes erstellen wir verschiedene Funktionen, um jede Benutzeraktion zu verarbeiten. Wenn sich das To-do im Bearbeitungsmodus befindet, kann der Benutzer die Änderungen speichern oder abbrechen. Wenn es sich nicht im Bearbeitungsmodus befindet, kann der Benutzer das To-do löschen, bearbeiten oder seinen Status zwischen abgeschlossen und aktiv umschalten.
Fügen Sie den folgenden Funktionssatz unter Ihrer vorherigen Funktion hinzu, um diese Aktionen zu bearbeiten:
jsfunction onCancel() { name = todo.name; // restores name to its initial value and editing = false; // and exit editing mode } function onSave() { update({ name }); // updates todo name editing = false; // and exit editing mode } function onRemove() { dispatch("remove", todo); // emit remove event } function onEdit() { editing = true; // enter editing mode } function onToggle() { update({ completed: !todo.completed }); // updates todo status }
Das Markup aktualisieren
Nun müssen wir das Markup unserer Todo
-Komponente aktualisieren, um die obigen Funktionen aufzurufen, wenn die entsprechenden Aktionen ausgeführt werden.
Um den Bearbeitungsmodus zu handhaben, verwenden wir die editing
-Variable, die ein boolescher Wert ist. Wenn sie true
ist, sollte sie das <input>
-Feld zum Bearbeiten des To-do-Namens und die Abbrechen- und Speichern-Buttons anzeigen. Wenn es sich nicht im Bearbeitungsmodus befindet, wird es das Kontrollkästchen, den To-do-Namen und die Buttons zum Bearbeiten und Löschen des To-dos anzeigen.
Um dies zu erreichen, verwenden wir einen if
-Block. Der if
-Block rendert bedingt einige Markup. Beachten Sie, dass er die Markup nicht einfach basierend auf der Bedingung zeigt oder verbirgt — er wird die Elemente je nach Bedingung dynamisch aus dem DOM hinzufügen und entfernen.
Wenn editing
true
ist, zeigt Svelte zum Beispiel das Aktualisierungsformular an; wenn es false
ist, wird es es aus dem DOM entfernen und das Kontrollkästchen hinzufügen. Dank Svelte-Mehrdeutigkeit reicht es aus, den Wert der editing
-Variable zuzuweisen, um die korrekten HTML-Elemente anzuzeigen.
Das folgende zeigt Ihnen eine Idee, wie die grundlegende Struktur des if
-Blocks aussieht:
<div class="stack-small">
{#if editing}
<!-- markup for editing to-do: label, input text, Cancel and Save Button -->
{:else}
<!-- markup for displaying to-do: checkbox, label, Edit and Delete Button -->
{/if}
</div>
Der nicht bearbeitende Abschnitt — das heißt, der {:else}
-Teil (untere Hälfte) des if
-Blocks — wird sehr ähnlich zu dem sein, den wir in unserer Todos
-Komponente hatten. Der einzige Unterschied ist, dass wir onToggle()
, onEdit()
und onRemove()
aufrufen, abhängig von der Benutzeraktion.
{:else}
<div class="c-cb">
<input type="checkbox" id="todo-{todo.id}"
on:click={onToggle} checked={todo.completed}
>
<label for="todo-{todo.id}" class="todo-label">{todo.name}</label>
</div>
<div class="btn-group">
<button type="button" class="btn" on:click={onEdit}>
Edit<span class="visually-hidden"> {todo.name}</span>
</button>
<button type="button" class="btn btn__danger" on:click={onRemove}>
Delete<span class="visually-hidden"> {todo.name}</span>
</button>
</div>
{/if}
</div>
Es ist erwähnenswert, dass:
- Wenn der Benutzer den Edit-Button drückt, führen wir
onEdit()
aus, das einfachediting
auftrue
setzt. - Wenn der Benutzer auf das Kontrollkästchen klickt, rufen wir die
onToggle()
-Funktion auf, dieupdate()
ausführt und ein Objekt mit dem neuencompleted
-Wert als Parameter übergibt. - Die
update()
-Funktion löst dasupdate
-Ereignis aus, wobei eine Kopie des ursprünglichen To-dos mit den angewendeten Änderungen als zusätzliche Informationen übergeben wird. - Schließlich löst die
onRemove()
-Funktion dasremove
-Ereignis aus und übergibt das zu löschendetodo
als zusätzliche Daten.
Die Bearbeitungs-UI (die obere Hälfte) enthält ein <input>
-Feld und zwei Buttons, um Änderungen zu speichern oder abzubrechen:
<div class="stack-small">
{#if editing}
<form on:submit|preventDefault={onSave} class="stack-small" on:keydown={(e) => e.key === 'Escape' && onCancel()}>
<div class="form-group">
<label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label>
<input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" />
</div>
<div class="btn-group">
<button class="btn todo-cancel" on:click={onCancel} type="button">
Cancel<span class="visually-hidden">renaming {todo.name}</span>
</button>
<button class="btn btn__primary todo-edit" type="submit" disabled={!name}>
Save<span class="visually-hidden">new name for {todo.name}</span>
</button>
</div>
</form>
{:else}
[...]
Wenn der Benutzer den Edit-Button drückt, wird die editing
-Variable auf true
gesetzt, und Svelte entfernt das Markup im {:else}
-Teil des DOMs und ersetzt es durch das Markup im {#if}
-Abschnitt.
Die value
-Eigenschaft des <input>
wird an die name
-Variable gebunden, und die Buttons zum Abbrechen und Speichern der Änderungen rufen onCancel()
bzw. onSave()
auf (wir haben diese Funktionen zuvor hinzugefügt):
- Wenn
onCancel()
aufgerufen wird, wirdname
auf seinen ursprünglichen Wert zurückgesetzt (wie bei der Übergabe als Eigenschaft) und wir verlassen den Bearbeitungsmodus (indem wirediting
auffalse
setzen). - Wenn
onSave()
aufgerufen wird, führen wir dieupdate()
-Funktion aus – übergeben den geändertenname
– und verlassen den Bearbeitungsmodus.
Wir deaktivieren den Save-Button auch, wenn das <input>
leer ist, indem wir das Attribut disabled={!name}
verwenden, und erlauben dem Benutzer, die Bearbeitung mit der Escape-Taste zu beenden, so:
on:keydown={(e) => e.key === 'Escape' && onCancel()}
Wir verwenden auch todo.id
, um eindeutige IDs für die neuen Eingabekontrollen und Labels zu erstellen.
-
Das vollständige aktualisierte Markup unserer
Todo
-Komponente sieht folgendermaßen aus. Aktualisieren Sie Ihre jetzt:svelte<div class="stack-small"> {#if editing} <!-- markup for editing todo: label, input text, Cancel and Save Button --> <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={(e) => e.key === 'Escape' && onCancel()}> <div class="form-group"> <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label> <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" /> </div> <div class="btn-group"> <button class="btn todo-cancel" on:click={onCancel} type="button"> Cancel<span class="visually-hidden">renaming {todo.name}</span> </button> <button class="btn btn__primary todo-edit" type="submit" disabled={!name}> Save<span class="visually-hidden">new name for {todo.name}</span> </button> </div> </form> {:else} <!-- markup for displaying todo: checkbox, label, Edit and Delete Button --> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={onToggle} checked={todo.completed} > <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn" on:click={onEdit}> Edit<span class="visually-hidden"> {todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={onRemove}> Delete<span class="visually-hidden"> {todo.name}</span> </button> </div> {/if} </div>
Hinweis: Wir könnten dies weiter in zwei verschiedene Komponenten aufteilen, eine für die Bearbeitung des To-dos und eine andere zum Anzeigen. Letztendlich hängt es davon ab, wie wohl Sie sich mit dieser Komplexität in einer einzigen Komponente fühlen. Sie sollten auch in Betracht ziehen, ob eine weitere Aufteilung es ermöglichen würde, diese Komponente in einem anderen Kontext wiederzuverwenden.
-
Um die Aktualisierungsfunktionalität zum Laufen zu bringen, müssen wir das
update
-Ereignis von derTodos
-Komponente aus behandeln. Fügen Sie in dessen<script>
-Abschnitt diesen Handler hinzu:jsfunction updateTodo(todo) { const i = todos.findIndex((t) => t.id === todo.id); todos[i] = { ...todos[i], ...todo }; }
Wir finden das
todo
in unseremtodos
-Array anhand vonid
und aktualisieren seinen Inhalt mit der Spread-Syntax. In diesem Fall hätten wir auch einfachtodos[i] = todo
verwenden können, aber diese Implementierung ist widerstandsfähiger und ermöglicht es derTodo
-Komponente, nur die aktualisierten Teile des To-dos zurückzugeben. -
Als Nächstes müssen wir dem
update
-Ereignis auf unserem<Todo>
-Komponentenaufruf lauschen und unsereupdateTodo()
-Funktion ausführen, wenn dies auftritt, um denname
- undcompleted
-Status zu ändern. Aktualisieren Sie Ihren <Todo>-Aufruf so:svelte{#each filterTodos(filter, todos) as todo (todo.id)} <li class="todo"> <Todo {todo} on:update={(e) => updateTodo(e.detail)} on:remove={(e) => removeTodo(e.detail)} /> </li>
-
Probieren Sie Ihre App erneut aus und Sie werden sehen, dass Sie To-dos hinzufügen, bearbeiten, löschen, die Bearbeitung abbrechen und den Erledigungsstatus umschalten können. Und unsere Statusüberschrift "x von y Aufgaben abgeschlossen" wird jetzt auch dann entsprechend aktualisiert, wenn To-dos abgeschlossen werden.
Wie Sie sehen können, ist es einfach, das "Props-Down, Events-Up"-Muster in Svelte zu implementieren. Dennoch kann bind
für einfache Komponenten eine gute Wahl sein; Svelte lässt Ihnen die Wahl.
Hinweis: Svelte bietet anspruchsvollere Mechanismen, um Informationen zwischen Komponenten zu teilen: die Context-API und Stores. Die Context-API bietet einen Mechanismus, um Komponenten und deren Nachkommen "miteinander sprechen" zu lassen, ohne Daten und Funktionen als Props zu übergeben oder viele Ereignisse auszulösen. Stores ermöglichen es Ihnen, reaktive Daten zwischen nicht hierarchisch verwandten Komponenten zu teilen. Wir werden uns Stores später in der Serie ansehen.
Der Code bisher
Git
Um den Stand des Codes zu sehen, wie er am Ende dieses Artikels aussehen sollte, greifen Sie auf Ihre Kopie unseres Repos folgendermaßen zu:
cd mdn-svelte-tutorial/05-advanced-concepts
Oder laden Sie den Inhalt des Ordners direkt herunter:
npx degit opensas/mdn-svelte-tutorial/05-advanced-concepts
Denken Sie daran, npm install && npm run dev
auszuführen, um Ihre App im Entwicklungsmodus zu starten.
REPL
Um den aktuellen Stand des Codes in einem REPL zu sehen, besuchen Sie:
https://svelte.dev/repl/76cc90c43a37452e8c7f70521f88b698?version=3.23.2
Zusammenfassung
Nun haben wir alle erforderlichen Funktionalitäten unserer App an Ort und Stelle. Wir können To-dos anzeigen, hinzufügen, bearbeiten und löschen, sie als abgeschlossen markieren und nach Status filtern.
In diesem Artikel haben wir die folgenden Themen behandelt:
- Extraktion von Funktionalität in eine neue Komponente
- Übergabe von Informationen von Kind zu Eltern durch einen als Eigenschaft empfangenen Handler
- Übergabe von Informationen von Kind zu Eltern über das
bind
-Directive - Bedingtes Rendern von Markup-Blöcken mithilfe des
if
-Blocks - Implementierung des "Props-Down, Events-Up"-Kommunikationsmusters
- Erstellen und Lauschen auf benutzerdefinierte Ereignisse
Im nächsten Artikel setzen wir die Komponentisierung unserer App fort und betrachten einige fortgeschrittene Techniken für die Arbeit mit dem DOM.