Unsere Svelte-App in Komponenten aufteilen
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 überschaubare Komponenten aufteilen und Informationen zwischen ihnen austauschen können. Wir werden unsere App in Komponenten unterteilen und dann weitere Funktionen hinzufügen, damit Benutzer bestehende Komponenten aktualisieren können.
Voraussetzungen: |
Es wird mindestens empfohlen, dass Sie mit den Kernsprachen HTML, CSS und JavaScript vertraut sind und Kenntnisse über die Terminal-/Kommandozeile haben. Sie benötigen ein Terminal mit installiertem Node und npm, um Ihre App zu kompilieren und zu bauen. |
---|---|
Ziel: | Zu lernen, wie Sie unsere App in Komponenten aufteilen und Informationen zwischen ihnen austauschen. |
Code mit uns
Git
Klonen Sie das GitHub-Repository (falls Sie es noch nicht getan haben) mit:
git clone https://github.com/opensas/mdn-svelte-tutorial.git
Um den aktuellen App-Zustand zu erreichen, ausführen
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 mit uns mit dem REPL zu coden, starten Sie unter
https://svelte.dev/repl/99b9eb228b404a2f8c8959b22c0a40d3?version=3.23.2
Die App in Komponenten aufteilen
In Svelte wird eine Anwendung aus einer oder mehreren Komponenten zusammengesetzt. Eine Komponente ist ein wiederverwendbarer, in sich geschlossener Codeblock, der HTML, CSS und JavaScript, die zusammen gehören, in einer .svelte
-Datei kapselt. 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 überschaubare Stücke zu organisieren. Dies hilft Ihnen zu verstehen, wie sie zueinander in Beziehung stehen, es fördert die Wiederverwendung und macht Ihren Code einfacher verständlich, wartbar und erweiterbar.
Aber wie wissen Sie, was in eine eigene Komponente aufgeteilt werden sollte?
Es gibt dafür keine festen Regeln. Einige Leute bevorzugen einen intuitiven Ansatz und beginnen, das Markup zu betrachten und um jede Komponente und Unterkomponente, die ihre eigene Logik zu haben scheint, Kästchen zu zeichnen.
Andere Leute wenden die gleichen Techniken an, die sie verwenden, um zu entscheiden, ob sie eine neue Funktion oder ein neues Objekt erstellen sollen. Eine solche Technik ist das Prinzip der Einzelverantwortung — das heißt, eine Komponente sollte idealerweise nur eine Sache tun. Wenn sie zu groß wird, sollte sie in kleinere Unterkomponenten aufgeteilt werden.
Beide Ansätze sollten sich gegenseitig ergänzen und Ihnen helfen, zu entscheiden, wie Sie Ihre Komponenten besser organisieren.
Letztendlich werden wir unsere App in die folgenden Komponenten aufteilen:
Alert.svelte
: Eine allgemeine Benachrichtigungsbox zur Kommunikation von Aktionen, die erfolgt sind.NewTodo.svelte
: Das Texteingabefeld und die Schaltfläche, die es Ihnen ermöglichen, ein neues To-do-Element einzugeben.FilterButton.svelte
: Die Alle, Aktiven und Abgeschlossenen Schaltflächen, die es Ihnen ermöglichen, Filter auf die angezeigten To-do-Elemente anzuwenden.TodosStatus.svelte
: Die "x von y Elementen abgeschlossen"-Überschrift.Todo.svelte
: Ein einzelnes To-do-Element. Jedes sichtbare To-do-Element wird in einer separaten Kopie dieser Komponente angezeigt.MoreActions.svelte
: Die Alle markieren und Abgeschlossene entfernen Schaltflächen am unteren Ende der Benutzeroberfläche, die es Ihnen ermöglichen, Massenaktionen auf die To-do-Elemente anzuwenden.
In diesem Artikel werden wir uns auf die Erstellung der FilterButton
- und Todo
-Komponenten konzentrieren; zu den anderen kommen wir in zukünftigen Artikeln.
Lassen Sie uns beginnen.
Hinweis: Im Prozess der Erstellung unserer ersten paar Komponenten werden wir auch verschiedene Techniken lernen, um zwischen Komponenten zu kommunizieren, und die Vor- und Nachteile jeder Technik.
Unser Filter-Komponente extrahieren
Wir beginnen mit der Erstellung unseres FilterButton.svelte
.
-
Erstellen Sie zunächst eine neue Datei,
components/FilterButton.svelte
. -
In dieser Datei werden wir eine
filter
-Prop deklarieren und dann das relevante Markup vonTodos.svelte
dorthin kopieren. 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>
-
Zurück in unserer
Todos.svelte
-Komponente möchten wir unsereFilterButton
-Komponente nutzen. Zuerst müssen wir sie importieren. Fügen Sie die folgende Zeile am Anfang desTodos.svelte <script>
-Abschnitts hinzu:jsimport FilterButton from "./FilterButton.svelte";
-
Ersetzen Sie den
<div class="filters...
-Element durch einen Aufruf derFilterButton
-Komponente, die den aktuellen Filter als Prop übernimmt. Die folgende Zeile ist alles, was Sie brauchen:svelte<FilterButton {filter} />
Hinweis:
Denken Sie daran, dass, wenn der HTML-Attributname und die Variable übereinstimmen, sie durch {variable}
ersetzt werden können. Deshalb konnten wir <FilterButton filter={filter} />
durch <FilterButton {filter} />
ersetzen.
Bisher läuft alles gut! Versuchen Sie jetzt, die App auszuprobieren. Sie werden feststellen, dass, wenn Sie auf die Filter-Schaltflächen klicken, sie ausgewählt sind und sich der Stil entsprechend aktualisiert. Aber wir haben ein Problem: Die To-dos werden nicht gefiltert. Das liegt daran, dass die filter
-Variable vom Todos
-Komponenten an die FilterButton
-Komponente durch die Prop fließt, aber Änderungen, die in der FilterButton
-Komponente auftreten, nicht zurück zu ihrem Elternteil fließen — das Datenbinding ist standardmäßig unidirektional. Lassen Sie uns einen Weg finden, dies zu lösen.
Daten zwischen Komponenten austauschen: Einen Handler als Prop übergeben
Eine Möglichkeit, Kinderkomponenten die Elternkomponenten über Änderungen zu benachrichtigen, besteht darin, einen Handler als Prop zu übergeben. Die Kinderkomponente führt den Handler aus, übergibt die benötigten Informationen als Parameter, und der Handler ändert den Zustand des Elternteils.
In unserem Fall wird die FilterButton
-Komponente einen onclick
-Handler von ihrem Elternteil erhalten. Immer wenn der Benutzer auf eine beliebige Filter-Schaltfläche klickt, wird das Kind den onclick
-Handler aufrufen und den ausgewählten Filter als Parameter zurück an seinen Elternteil übergeben.
Wir werden einfach die onclick
-Prop deklarieren, indem wir einen Dummy-Handler zuweisen, um Fehler zu vermeiden, 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 jetzt das
FilterButton
innerhalb vonTodos.svelte
aufrufen, müssen wir den Handler angeben. Aktualisieren Sie es so:svelte<FilterButton {filter} onclick={ (clicked) => filter = clicked }/>
Wenn eine beliebige Filter-Schaltfläche geklickt wird, aktualisieren wir einfach die Filter-Variable mit dem neuen Filter. Jetzt wird unsere FilterButton
-Komponente wieder funktionieren.
Einfachere bidirektionale Datenbindung mit dem bind
-Direktiv
Im vorhergehenden Beispiel haben wir festgestellt, dass unsere FilterButton
-Komponente nicht funktionierte, weil unser Anwendungsstatus von Elternteil zu Kind durch die filter
-Prop floss, aber nicht wieder nach oben. Also haben wir eine onclick
-Prop hinzugefügt, um dem Kinderkomponent zu ermöglichen, den neuen filter
-Wert an ihren Elternteil zu kommunizieren.
Es funktioniert in Ordnung, aber Svelte bietet uns eine einfachere und direktere Möglichkeit, um eine bidirektionale Datenbindung zu erreichen. Daten fließen normalerweise von Elternteil zu Kind mit Props. Wenn wir auch möchten, dass sie in die andere Richtung fließen, von Kind zu Elternteil, können wir das bind:
-Direktiv verwenden.
Mit bind
teilen wir Svelte mit, dass alle Änderungen, die an der filter
-Prop in der FilterButton
-Komponente vorgenommen werden, wieder zum Elternteil propagiert werden sollen. Das heißt, wir binden den Wert der filter
-Variable im Elternteil an ihren Wert im Kind.
-
In
Todos.svelte
aktualisieren Sie den Aufruf derFilterButton
-Komponente wie folgt:svelte<FilterButton bind:filter={filter} />
Wie üblich bietet Svelte uns eine schöne Abkürzung:
bind:value={value}
ist äquivalent zubind:value
. Im obigen Beispiel könnten Sie also einfach<FilterButton bind:filter />
schreiben. -
Die Kinderkomponente kann jetzt den Wert der Filter-Variable des Elternelements ändern, daher benötigen wir die
onclick
-Prop nicht mehr. Ändern Sie das<script>
-Element IhresFilterButton
folgendermaßen:svelte<script> export let filter = "all"; </script>
-
Versuchen Sie erneut Ihre App, und Sie sollten sehen, dass Ihre Filter weiterhin korrekt funktionieren.
Erstellung unserer Todo-Komponente
Jetzt erstellen wir eine Todo
-Komponente, um jedes einzelne To-do, einschließlich des Kontrollkästchens und der Bearbeitungslogik, so zu kapseln, dass Sie ein bestehendes To-do ändern können.
Unsere Todo
-Komponente erhält ein einzelnes todo
-Objekt als Prop. Lassen Sie uns die todo
-Prop deklarieren und den Code aus der Todos
-Komponente verschieben. Nur für den Moment ersetzen wir den Aufruf zu removeTodo
durch einen Alarm. Wir fügen diese Funktionalität später wieder hinzu.
-
Erstellen Sie eine neue Komponentendatei,
components/Todo.svelte
. -
Fügen Sie den folgenden Inhalt 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>
-
Jetzt 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 ein:jsimport Todo from "./Todo.svelte";
-
Als nächstes müssen wir unseren
{#each}
Block aktualisieren, um eine<Todo>
-Komponente für jedes To-do aufzunehmen, anstatt des Codes, der inTodo.svelte
verschoben wurde. Wir übergeben auch das aktuelletodo
-Objekt als Prop 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 einige zu aktivieren/deaktivieren und beobachten, dass die Filter weiterhin wie erwartet funktionieren), aber unsere "x von y Elementen abgeschlossen"-Statusüberschrift wird nicht mehr entsprechend aktualisiert. Das liegt daran, dass unsere Todo
-Komponente das To-Do über die Prop erhält, aber keine Informationen zurück an ihren Elternteil sendet. Wir werden dies später beheben.
Daten zwischen Komponenten austauschen: Props-down, Events-up-Muster
Das bind
-Direktiv ist ziemlich unkompliziert und ermöglicht es Ihnen, Daten zwischen einer Eltern- und einer Kinderkomponente mit minimalem Aufwand auszutauschen. Wenn Ihre Anwendung jedoch größer und komplexer wird, kann es leicht schwierig werden, alle gebundenen Werte im Auge zu behalten. Ein anderer Ansatz ist das "props-down, events-up" Kommunikationsmuster.
Grundsätzlich basiert dieses Muster darauf, dass Kinderkomponenten Daten von ihren Eltern über Props erhalten und Elternkomponenten ihren Zustand aktualisieren, indem sie Ereignisse behandeln, die von Kinderkomponenten gesendet werden. Props fließen nach unten von Elternteil zu Kind, und Ereignisse blubbern nach oben von Kind zu Elternteil. Dieses Muster etabliert einen bidirektionalen Informationsfluss, der vorhersehbar und einfacher zu verstehen ist.
Lassen Sie uns anschauen, wie wir unsere eigenen Ereignisse senden, um die fehlende Löschen-Button-Funktionalität erneut zu implementieren.
Um benutzerdefinierte Ereignisse zu erstellen, verwenden wir den createEventDispatcher
-Dienst. Dies wird eine dispatch()
-Funktion zurückgeben, die es uns ermöglicht, benutzerdefinierte Ereignisse zu senden. Wenn Sie ein Ereignis senden, müssen Sie den Namen des Ereignisses und optional ein Objekt mit zusätzlichen Informationen, die Sie an jeden Listener weitergeben möchten, übergeben. 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. Darüber hinaus können Sie ein Ereignis an Ihre Elternkomponente blubbern lassen, indem Sie on:event
ohne einen Handler angeben.
Wir werden unsere Todo
-Komponente bearbeiten, um ein remove
-Ereignis zu senden, wobei das zu entfernende To-Do als zusätzliche Information übermittelt wird.
-
Fügen Sie zunächst die folgenden Zeilen zum Anfang des
<script>
-Abschnitts derTodo
-Komponente hinzu:jsimport { createEventDispatcher } from "svelte"; const dispatch = createEventDispatcher();
-
Aktualisieren Sie jetzt den Löschen-Button im Markup-Bereich derselben Datei wie folgt:
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)
senden wir einremove
-Ereignis und übergeben als zusätzliche Daten das zu löschendetodo
. Der Handler wird mit einem Ereignisobjekt aufgerufen, wobei die zusätzlichen Daten in derevent.detail
-Eigenschaft verfügbar sind. -
Jetzt müssen wir dieses Ereignis von innerhalb
Todos.svelte
anhören 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
e
-Parameter (das Ereignisobjekt), welches wie zuvor beschrieben das zu löschende To-Do in derdetail
-Eigenschaft hält. -
Wenn Sie jetzt 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 Elternteil, so dass unsere "x von y Elementen abgeschlossen"-Statusüberschrift jetzt entsprechend aktualisiert wird, wenn To-Dos gelöscht werden.
Nun kümmern wir uns um das update
-Ereignis, damit unsere Elternkomponente über alle Änderungen eines To-Dos benachrichtigt werden kann.
To-Dos aktualisieren
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 einfügen. Beim Eintritt in den Bearbeitungsmodus zeigen wir ein <input>
-Feld an, das es uns ermöglicht, den aktuellen To-Do-Namen zu bearbeiten, mit zwei Schaltflächen, um unsere Änderungen zu bestätigen oder abzubrechen.
Die Ereignisse behandeln
-
Wir benötigen eine Variable, um zu verfolgen, ob wir uns im Bearbeitungsmodus befinden, und eine andere, um den Namen der zu aktualisierenden Aufgabe zu speichern. 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 senden wird:- Wir könnten unterschiedliche Ereignisse für den Statuswechsel und die Bearbeitung des Namens senden (zum Beispiel
updateTodoStatus
undupdateTodoName
). - Oder wir könnten einen allgemeineren Ansatz wählen und ein einzelnes
update
-Ereignis für beide Operationen senden.
Wir wählen den zweiten Ansatz, 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 Aktualisierungen mit demselben Ereignis handhaben können.
Lassen Sie uns eine
update()
-Funktion erstellen, die die Änderungen empfängt und ein Update-Ereignis mit dem geänderten To-Do sendet. Fügen Sie die folgende Funktion ebenfalls 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 ursprüngliche To-Do mit den darauf angewandten Änderungen zurückzugeben.
- Wir könnten unterschiedliche Ereignisse für den Statuswechsel und die Bearbeitung des Namens senden (zum Beispiel
-
Als nächstes erstellen wir verschiedene Funktionen, um jede Benutzeraktion zu behandeln. Wenn das To-Do im Bearbeitungsmodus ist, kann der Benutzer Ä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 die folgenden Funktionen unterhalb Ihrer vorherigen Funktion hinzu, um diese Aktionen zu behandeln:
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
Jetzt 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 steuern, verwenden wir die editing
-Variable, die ein Boolean ist. Wenn sie true
ist, sollte sie das <input>
-Feld für die Bearbeitung des To-Do-Namens sowie die Abbrechen und Speichern-Buttons anzeigen. Wenn es sich nicht im Bearbeitungsmodus befindet, zeigt sie das Checkbox, den To-Do-Namen und die Buttons zum Bearbeiten und Löschen des To-Dos an.
Um dies zu erreichen, verwenden wir einen if
-Block. Der if
-Block rendert bedingt ein Markup. Beachten Sie, dass er das Markup nicht nur basierend auf der Bedingung zeigt oder versteckt — je nach Bedingung fügt er die Elemente aus dem DOM hinzu oder entfernt sie.
Wenn editing
true
ist, zeigt Svelte zum Beispiel das Aktualisierungsformular; wenn es false
ist, entfernt es es aus dem DOM und fügt das Kontrollkästchen hinzu. Dank der Svelte-Reaktivität reicht es aus, den Wert der editing
-Variable zuzuweisen, um die korrekten HTML-Elemente anzuzeigen.
Das folgende Beispiel gibt Ihnen eine Vorstellung davon, 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 dem sehr ähnlich sein, den wir in unserer Todos
-Komponente hatten. Der einzige Unterschied besteht darin, dass wir onToggle()
, onEdit()
, und onRemove()
je nach Benutzeraktion aufrufen.
{: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 Bearbeiten-Button drückt, führen wir
onEdit()
aus, das einfach dieediting
-Variable 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 sendet dasupdate
-Ereignis und übergibt als zusätzliche Information eine Kopie des ursprünglichen To-Dos mit den darauf angewandten Änderungen. - Schließlich sendet die
onRemove()
-Funktion dasremove
-Ereignis und übergibt das zu löschendetodo
als zusätzliche Daten.
Die Bearbeitungsoberfläche (die obere Hälfte) wird ein <input>
-Feld und zwei Schaltflächen zum Abbrechen oder Speichern der Änderungen enthalten:
<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 Bearbeiten-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 Eigenschaft value
des <input>
wird an die Variable name
gebunden, und die Schaltflächen zum Abbrechen und Speichern der Änderungen rufen onCancel()
bzw. onSave()
auf (wir haben diese Funktionen vorher hinzugefügt):
- Wenn
onCancel()
aufgerufen wird, wirdname
auf seinen ursprünglichen Wert (wenn als Prop übergeben) zurückgesetzt und wir verlassen den Bearbeitungsmodus (indem wirediting
auffalse
setzen). - Wenn
onSave()
aufgerufen wird, führen wir dieupdate()
-Funktion aus — übergeben den modifiziertenname
— und verlassen den Bearbeitungsmodus.
Wir deaktivieren auch den Speichern-Button, wenn das <input>
leer ist, indem wir das disabled={!name}
-Attribut verwenden, und erlauben dem Benutzer, die Bearbeitung mit der Escape-Taste abzubrechen, so:
on:keydown={(e) => e.key === 'Escape' && onCancel()}
Wir verwenden auch todo.id
, um eindeutige IDs für die neuen Eingabesteuerungen und Etiketten 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 zum Bearbeiten des To-Dos und eine andere zum Anzeigen desselben. Letztlich hängt es davon ab, wie komfortabel Sie sich fühlen, mit diesem Komplexitätsgrad in einer einzigen Komponente umzugehen. Sie sollten auch überlegen, ob eine weitere Aufteilung eine erneute Verwendung dieser Komponente in einem anderen Kontext ermöglichen würde.
-
Um die Update-Funktionalität zum Laufen zu bringen, müssen wir das
update
-Ereignis von derTodos
-Komponente behandeln. Fügen Sie im<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
nachid
in unseremtodos
-Array und aktualisieren dessen Inhalt unter Verwendung der Spread-Syntax. In diesem Fall könnten wir auch einfachtodos[i] = todo
verwenden, aber diese Implementierung ist robuster und ermöglicht derTodo
-Komponente, nur die aktualisierten Teile des To-Dos zurückzugeben. -
Als Nächstes müssen wir das
update
-Ereignis bei unserem<Todo>
-Komponentenaufruf hören und unsereupdateTodo()
-Funktion ausführen, wenn dies auftritt, umname
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>
-
Versuchen Sie Ihre App erneut, und Sie sollten sehen, dass Sie To-Dos löschen, hinzufügen, bearbeiten, die Bearbeitung abbrechen und den Fertigstellungsstatus umschalten können. Und unsere "x von y Elementen abgeschlossen"-Statusüberschrift wird jetzt 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 fortgeschrittenere Mechanismen, um Informationen unter Komponenten auszutauschen: die Kontext-API und Stores. Die Kontext-API bietet einen Mechanismus für Komponenten und ihre Nachkommen, um "miteinander zu sprechen", ohne Daten und Funktionen als Props weiterzugeben oder viele Ereignisse zu senden. Stores erlauben es Ihnen, reaktive Daten zwischen Komponenten zu teilen, die nicht hierarchisch verbunden sind. Wir werden später in der Serie Stores genauer betrachten.
Der Code bisher
Git
Um den Zustand des Codes zu sehen, wie er am Ende dieses Artikels sein sollte, greifen Sie auf Ihre Kopie unseres Repos so 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 im REPL zu sehen, besuchen Sie:
https://svelte.dev/repl/76cc90c43a37452e8c7f70521f88b698?version=3.23.2
Zusammenfassung
Jetzt haben wir alle erforderlichen Funktionen unserer App implementiert. 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:
- Funktionalität in eine neue Komponente extrahieren
- Informationen vom Kind zum Elternteil über einen als Prop empfangenen Handler übergeben
- Informationen mit dem
bind
-Direktiv vom Kind zum Elternteil übergeben - Bedingtes Rendern von Markup-Blöcken mit dem
if
-Block - Das "props-down, events-up"-Kommunikationsmuster implementieren
- Benutzerdefinierte Ereignisse erstellen und darauf hören
Im nächsten Artikel werden wir unsere App weiter in Komponenten aufteilen und einige fortgeschrittene Techniken für die Arbeit mit dem DOM betrachten.