Starten unserer Svelte-Tasklisten-App
Jetzt, da wir ein grundlegendes Verständnis dafür haben, wie Dinge in Svelte funktionieren, können wir mit dem Bau unserer Beispiel-App beginnen: einer Taskliste. In diesem Artikel werden wir zuerst einen Blick auf die gewünschte Funktionalität unserer App werfen, und dann werden wir eine Todos.svelte
-Komponente erstellen und statisches Markup und Stile einfügen, sodass alles bereit ist, um mit der Entwicklung der Funktionen unserer Tasklisten-App zu beginnen, auf die wir in den folgenden Artikeln eingehen werden.
Wir möchten, dass unsere Nutzer in der Lage sind, Aufgaben zu durchsuchen, hinzuzufügen und zu löschen, und sie auch als abgeschlossen zu markieren. Dies wird die grundlegende Funktionalität sein, die wir in dieser Tutorial-Reihe entwickeln werden, und wir werden dabei auch einige fortgeschrittenere Konzepte betrachten.
Voraussetzungen: |
Es wird mindestens empfohlen, dass Sie mit den Kernsprachen HTML, CSS und JavaScript vertraut sind und Kenntnisse über die Terminal-/Kommandozeilehaben. Sie benötigen ein Terminal mit installiertem node + npm, um Ihre App zu kompilieren und zu bauen. |
---|---|
Ziel: | Zu lernen, wie man eine Svelte-Komponente erstellt, sie in einer anderen Komponente rendert, Daten über Props hineinreicht und ihren Zustand speichert. |
Gemeinsam mit uns programmieren
Git
Klonen Sie das GitHub-Repository (wenn 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/02-starting-our-todo-app
Oder laden Sie direkt den Inhalt des Ordners herunter:
npx degit opensas/mdn-svelte-tutorial/02-starting-our-todo-app
Vergessen Sie nicht, npm install && npm run dev
auszuführen, um Ihre App im Entwicklungsmodus zu starten.
REPL
Um mit uns über den REPL zu programmieren, beginnen Sie bei
https://svelte.dev/repl/b7b831ea3a354d3789cefbc31e2ca495?version=3.23.2
Aufgabenlisten-App-Funktionen
So wird unsere Aufgabenlisten-App aussehen, sobald sie fertig ist:
Mit dieser Benutzeroberfläche kann unser Nutzer:
- Seine Aufgaben durchsuchen
- Aufgaben als abgeschlossen/offen markieren, ohne sie zu löschen
- Aufgaben entfernen
- Neue Aufgaben hinzufügen
- Aufgaben nach Status filtern: alle Aufgaben, aktive Aufgaben oder abgeschlossene Aufgaben
- Aufgaben bearbeiten
- Alle Aufgaben als aktiv/abgeschlossen markieren
- Alle abgeschlossenen Aufgaben entfernen
Unsere erste Komponente bauen
Lassen Sie uns eine Todos.svelte
-Komponente erstellen. Diese wird unsere Aufgabenliste enthalten.
-
Erstellen Sie einen neuen Ordner —
src/components
.Hinweis: Sie können Ihre Komponenten überall innerhalb des
src
-Ordners ablegen, aber dercomponents
-Ordner ist eine anerkannte Konvention, die es Ihnen ermöglicht, Ihre Komponenten leicht zu finden. -
Erstellen Sie eine Datei namens
src/components/Todos.svelte
mit folgendem Inhalt:svelte<h1>Svelte to-do list</h1>
-
Ändern Sie das
title
-Element inpublic/index.html
, um den Text Svelte-Aufgabenliste zu enthalten:svelte<title>Svelte to-do list</title>
-
Öffnen Sie
src/App.svelte
und ersetzen Sie dessen Inhalt durch Folgendes:svelte<script> import Todos from "./components/Todos.svelte"; </script> <Todos />
-
Im Entwicklungsmodus gibt Svelte eine Warnung in der Browserkonsole aus, wenn Sie eine Prop angeben, die in der Komponente nicht existiert; in diesem Fall haben wir eine
name
-Prop, die festgelegt wird, wenn wir dieApp
-Komponente insrc/main.js
instanziieren, die innerhalb vonApp
nicht verwendet wird. Die Konsole sollte Ihnen derzeit eine Meldung wie "<App> wurde mit unbekannter Prop 'name' erstellt" geben. Um dies zu beheben, entfernen Sie diename
-Prop aussrc/main.js
; sie sollte jetzt folgendermaßen aussehen:jsimport App from "./App.svelte"; const app = new App({ target: document.body, }); export default app;
Wenn Sie jetzt Ihre Testserver-URL überprüfen, sehen Sie unsere Todos.svelte
-Komponente, die gerendert wird:
Statisches Markup hinzufügen
Momentan beginnen wir mit einer statischen Markup-Darstellung unserer App, damit Sie sehen können, wie sie aussehen wird. Kopieren und fügen Sie Folgendes in unsere Todos.svelte
-Komponentendatei ein, und ersetzen Sie den bestehenden Inhalt:
<!-- Todos.svelte -->
<div class="todoapp stack-large">
<!-- NewTodo -->
<form>
<h2 class="label-wrapper">
<label for="todo-0" class="label__lg"> What needs to be done? </label>
</h2>
<input type="text" id="todo-0" autocomplete="off" class="input input__lg" />
<button type="submit" disabled="" class="btn btn__primary btn__lg">
Add
</button>
</form>
<!-- Filter -->
<div class="filters btn-group stack-exception">
<button class="btn toggle-btn" aria-pressed="true">
<span class="visually-hidden">Show</span>
<span>All</span>
<span class="visually-hidden">tasks</span>
</button>
<button class="btn toggle-btn" aria-pressed="false">
<span class="visually-hidden">Show</span>
<span>Active</span>
<span class="visually-hidden">tasks</span>
</button>
<button class="btn toggle-btn" aria-pressed="false">
<span class="visually-hidden">Show</span>
<span>Completed</span>
<span class="visually-hidden">tasks</span>
</button>
</div>
<!-- TodosStatus -->
<h2 id="list-heading">2 out of 3 items completed</h2>
<!-- Todos -->
<ul role="list" class="todo-list stack-large" aria-labelledby="list-heading">
<!-- todo-1 (editing mode) -->
<li class="todo">
<div class="stack-small">
<form class="stack-small">
<div class="form-group">
<label for="todo-1" class="todo-label">
New name for 'Create a Svelte starter app'
</label>
<input
type="text"
id="todo-1"
autocomplete="off"
class="todo-text" />
</div>
<div class="btn-group">
<button class="btn todo-cancel" type="button">
Cancel
<span class="visually-hidden">renaming Create a Svelte starter app</span>
</button>
<button class="btn btn__primary todo-edit" type="submit">
Save
<span class="visually-hidden">new name for Create a Svelte starter app</span>
</button>
</div>
</form>
</div>
</li>
<!-- todo-2 -->
<li class="todo">
<div class="stack-small">
<div class="c-cb">
<input type="checkbox" id="todo-2" checked />
<label for="todo-2" class="todo-label">
Create your first component
</label>
</div>
<div class="btn-group">
<button type="button" class="btn">
Edit
<span class="visually-hidden">Create your first component</span>
</button>
<button type="button" class="btn btn__danger">
Delete
<span class="visually-hidden">Create your first component</span>
</button>
</div>
</div>
</li>
<!-- todo-3 -->
<li class="todo">
<div class="stack-small">
<div class="c-cb">
<input type="checkbox" id="todo-3" />
<label for="todo-3" class="todo-label">
Complete the rest of the tutorial
</label>
</div>
<div class="btn-group">
<button type="button" class="btn">
Edit
<span class="visually-hidden">Complete the rest of the tutorial</span>
</button>
<button type="button" class="btn btn__danger">
Delete
<span class="visually-hidden">Complete the rest of the tutorial</span>
</button>
</div>
</div>
</li>
</ul>
<hr />
<!-- MoreActions -->
<div class="btn-group">
<button type="button" class="btn btn__primary">Check all</button>
<button type="button" class="btn btn__primary">Remove completed</button>
</div>
</div>
Überprüfen Sie die Darstellung noch einmal, und Sie werden etwas wie dies sehen:
Das obige HTML-Markup ist nicht sehr schön gestaltet und auch funktional nutzlos. Dennoch lassen Sie uns einen Blick auf das Markup werfen und sehen, wie es sich auf unsere gewünschten Funktionen bezieht:
- Ein Label und ein Textfeld zum Eingeben neuer Aufgaben
- Drei Schaltflächen zum Filtern nach Aufgabentatus
- Ein Label, das die Gesamtzahl der Aufgaben und die abgeschlossenen Aufgaben anzeigt
- Eine ungeordnete Liste, die ein Listenelement für jede Aufgabe enthält
- Wenn die Aufgabe bearbeitet wird, hat das Listenelement ein Eingabefeld und zwei Schaltflächen zum Abbrechen oder Speichern von Änderungen
- Wenn die Aufgabe nicht bearbeitet wird, gibt es ein Kontrollkästchen zum Festlegen des abgeschlossenen Status und zwei Schaltflächen zum Bearbeiten oder Löschen der Aufgabe
- Schließlich gibt es zwei Schaltflächen zum Markieren/Entmarkieren aller Aufgaben und zum Entfernen erledigter Aufgaben
In den folgenden Artikeln werden wir all diese Funktionen und mehr zum Laufen bringen.
Zugänglichkeitsmerkmale der Aufgabenliste
Sie mögen hier einige ungewöhnliche Attribute bemerken. Zum Beispiel:
<button class="btn toggle-btn" aria-pressed="true">
<span class="visually-hidden">Show</span>
<span>All</span>
<span class="visually-hidden">tasks</span>
</button>
Hier teilt aria-pressed
unterstützenden Technologien (wie Screenreadern) mit, dass die Schaltfläche in einem von zwei Zuständen sein kann: gedrückt
oder ungedrückt
. Betrachten Sie diese als Analogien für ein und aus. Durch das Setzen eines Wertes von true
bedeutet dies, dass die Schaltfläche standardmäßig gedrückt ist.
Die Klasse visually-hidden
hat momentan keine Wirkung, da wir noch keine CSS-Stile eingebunden haben. Sobald wir unsere Stile an Ort und Stelle haben, wird jedes Element mit dieser Klasse für sehende Benutzer versteckt und weiterhin für Screenreader-Benutzer verfügbar sein — dies liegt daran, dass diese Wörter von sehenden Benutzern nicht benötigt werden; sie sollen weitere Informationen darüber geben, was die Schaltfläche für Screenreader-Benutzer tut, die nicht den zusätzlichen visuellen Kontext haben, der ihnen hilft.
Weiter unten können Sie das folgende <ul>
-Element finden:
<ul
role="list"
class="todo-list stack-large"
aria-labelledby="list-heading">
Das role
-Attribut hilft unterstützenden Technologien zu erklären, welche Art von semantischem Wert ein Element hat — oder was sein Zweck ist. Ein <ul>
wird standardmäßig wie eine Liste behandelt, aber die Stile, die wir hinzufügen werden, werden diese Funktionalität brechen. Diese Rolle wird die "Listen"-Bedeutung für das <ul>
-Element wiederherstellen. Wenn Sie mehr darüber erfahren möchten, warum dies notwendig ist, können Sie Scott O'Haras Artikel "Fixing Lists" (2019) lesen.
Das aria-labelledby
-Attribut teilt unterstützenden Technologien mit, dass wir unser <h2>
mit einem id
von list-heading
als das Label behandeln, das den Zweck der Liste darunter beschreibt. Diese Verbindung gibt der Liste einen informativen Kontext, der Screenreader-Benutzern helfen könnte, den Zweck besser zu verstehen.
Dies scheint ein guter Zeitpunkt zu sein, um darüber zu sprechen, wie Svelte mit Barrierefreiheit umgeht; lassen Sie uns das jetzt tun.
Svelte-Unterstützung für Barrierefreiheit
Svelte legt besonderen Wert auf Barrierefreiheit. Die Absicht ist es, Entwickler dazu zu ermutigen, "standardmäßig" zugänglichere Code zu schreiben. Als Compiler kann Svelte unsere HTML-Vorlagen statisch analysieren, um Warnungen zur Barrierefreiheit auszugeben, wenn Komponenten kompiliert werden.
Barrierefreiheit (abgekürzt a11y) ist nicht immer leicht richtig umzusetzen, aber Svelte hilft, indem es Sie warnt, wenn Sie unzugängliches Markup schreiben.
Zum Beispiel, wenn wir ein <img>
-Element zu unserer todos.svelte
-Komponente ohne das entsprechende alt
-Prop hinzufügen:
<h1>Svelte To-Do list</h1>
<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" />
Der Compiler gibt die folgende Warnung aus:
(!) Plugin svelte: A11y: <img> element should have an alt attribute
src/components/Todos.svelte
1: <h1>Svelte To-Do list</h1>
2:
3: <img height="32" width="88" src="https://www.w3.org/WAI/wcag2A">
^
created public/build/bundle.js in 220ms
[2020-07-15 04:07:43] waiting for changes...
Darüber hinaus kann unser Editor diese Warnung anzeigen, noch bevor wir den Compiler aufrufen:
Sie können Svelte anweisen, diese Warnung für den nächsten Markup-Block mit einem Kommentar zu ignorieren, der mit svelte-ignore
beginnt, wie folgt:
<!-- svelte-ignore a11y-missing-attribute -->
<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" />
Hinweis: Mit VS Code können Sie diesen Ignore-Kommentar automatisch hinzufügen, indem Sie auf den Schnellkorrektur…-Link klicken oder Strg + . drücken.
Wenn Sie diese Warnung global deaktivieren möchten, können Sie diesen onwarn
-Handler zu Ihrer rollup.config.js
-Datei in der Konfiguration für das Svelte
-Plugin hinzufügen, wie folgt:
plugins: [
svelte({
dev: !production,
css: (css) => {
css.write("public/build/bundle.css");
},
// Warnings are normally passed straight to Rollup. You can
// optionally handle them here, for example to squelch
// warnings with a particular code
onwarn: (warning, handler) => {
// e.g. I don't care about screen readers -> please DON'T DO THIS!!!
if (warning.code === "a11y-missing-attribute") {
return;
}
// let Rollup handle all other warnings normally
handler(warning);
},
}),
// …
];
Diese Warnungen sind absichtlich im Compiler selbst und nicht als Plug-in implementiert, das Sie möglicherweise Ihrem Projekt hinzufügen möchten. Die Idee ist, standardmäßig a11y-Probleme in Ihrem Markup zu überprüfen und Sie können von spezifischen Warnungen abweichen.
Hinweis: Sie sollten diese Warnungen nur deaktivieren, wenn Sie gute Gründe dafür haben, zum Beispiel beim Aufbau eines schnellen Prototyps. Es ist wichtig, ein guter Web-Gebietsgenosse zu sein und Ihre Seiten der breitest möglichen Benutzerbasis zugänglich zu machen.
Die von Svelte geprüften Barrierefreiheitsregeln stammen aus eslint-plugin-jsx-a11y, einem Plug-in für ESLint, das statische Prüfungen für viele Barrierefreiheitsregeln auf JSX-Elementen bereitstellt. Svelte zielt darauf ab, alle davon in seinem Compiler zu implementieren, und die meisten wurden bereits nach Svelte portiert. Auf GitHub können Sie sehen, welche Barrierefreiheitsprüfungen noch fehlen. Sie können die Bedeutung jeder Regel überprüfen, indem Sie auf ihren Link klicken.
Stil für unser Markup
Lassen Sie uns die Aufgabenliste etwas besser aussehen lassen. Ersetzen Sie den Inhalt der Datei public/global.css
durch Folgendes:
/* RESETS */
*,
*::before,
*::after {
box-sizing: border-box;
}
*:focus {
outline: 3px dashed #228bec;
outline-offset: 0;
}
html {
font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
margin-bottom: 0;
}
ul {
list-style: none;
padding: 0;
}
button {
border: none;
margin: 0;
padding: 0;
width: auto;
overflow: visible;
background: transparent;
color: inherit;
font: inherit;
line-height: normal;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
appearance: none;
}
button::-moz-focus-inner {
border: 0;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button,
input {
overflow: visible;
}
input[type="text"] {
border-radius: 0;
}
body {
width: 100%;
max-width: 68rem;
margin: 0 auto;
font:
1.6rem/1.25 Arial,
sans-serif;
background-color: #f5f5f5;
color: #4d4d4d;
}
@media screen and (min-width: 620px) {
body {
font-size: 1.9rem;
line-height: 1.31579;
}
}
/*END RESETS*/
/* GLOBAL STYLES */
.form-group > input[type="text"] {
display: inline-block;
margin-top: 0.4rem;
}
.btn {
padding: 0.8rem 1rem 0.7rem;
border: 0.2rem solid #4d4d4d;
cursor: pointer;
text-transform: capitalize;
}
.btn.toggle-btn {
border-width: 1px;
border-color: #d3d3d3;
}
.btn.toggle-btn[aria-pressed="true"] {
text-decoration: underline;
border-color: #4d4d4d;
}
.btn__danger {
color: #fff;
background-color: #ca3c3c;
border-color: #bd2130;
}
.btn__filter {
border-color: lightgrey;
}
.btn__primary {
color: #fff;
background-color: #000;
}
.btn__primary:disabled {
color: darkgrey;
background-color: #565656;
}
.btn-group {
display: flex;
justify-content: space-between;
}
.btn-group > * {
flex: 1 1 49%;
}
.btn-group > * + * {
margin-left: 0.8rem;
}
.label-wrapper {
margin: 0;
flex: 0 0 100%;
text-align: center;
}
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
}
[class*="stack"] > * {
margin-top: 0;
margin-bottom: 0;
}
.stack-small > * + * {
margin-top: 1.25rem;
}
.stack-large > * + * {
margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
.stack-small > * + * {
margin-top: 1.4rem;
}
.stack-large > * + * {
margin-top: 2.8rem;
}
}
.stack-exception {
margin-top: 1.2rem;
}
/* END GLOBAL STYLES */
.todoapp {
background: #fff;
margin: 2rem 0 4rem 0;
padding: 1rem;
position: relative;
box-shadow:
0 2px 4px 0 rgb(0 0 0 / 20%),
0 2.5rem 5rem 0 rgb(0 0 0 / 10%);
}
@media screen and (min-width: 550px) {
.todoapp {
padding: 4rem;
}
}
.todoapp > * {
max-width: 50rem;
margin-left: auto;
margin-right: auto;
}
.todoapp > form {
max-width: 100%;
}
.todoapp > h1 {
display: block;
max-width: 100%;
text-align: center;
margin: 0;
margin-bottom: 1rem;
}
.label__lg {
line-height: 1.01567;
font-weight: 300;
padding: 0.8rem;
margin-bottom: 1rem;
text-align: center;
}
.input__lg {
padding: 2rem;
border: 2px solid #000;
}
.input__lg:focus {
border-color: #4d4d4d;
box-shadow: inset 0 0 0 2px;
}
[class*="__lg"] {
display: inline-block;
width: 100%;
font-size: 1.9rem;
}
[class*="__lg"]:not(:last-child) {
margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
[class*="__lg"] {
font-size: 2.4rem;
}
}
.filters {
width: 100%;
margin: unset auto;
}
/* Todo item styles */
.todo {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.todo > * {
flex: 0 0 100%;
}
.todo-text {
width: 100%;
min-height: 4.4rem;
padding: 0.4rem 0.8rem;
border: 2px solid #565656;
}
.todo-text:focus {
box-shadow: inset 0 0 0 2px;
}
/* CHECKBOX STYLES */
.c-cb {
box-sizing: border-box;
font-family: Arial, sans-serif;
-webkit-font-smoothing: antialiased;
font-weight: 400;
font-size: 1.6rem;
line-height: 1.25;
display: block;
position: relative;
min-height: 44px;
padding-left: 40px;
clear: left;
}
.c-cb > label::before,
.c-cb > input[type="checkbox"] {
box-sizing: border-box;
top: -2px;
left: -2px;
width: 44px;
height: 44px;
}
.c-cb > input[type="checkbox"] {
-webkit-font-smoothing: antialiased;
cursor: pointer;
position: absolute;
z-index: 1;
margin: 0;
opacity: 0;
}
.c-cb > label {
font-size: inherit;
font-family: inherit;
line-height: inherit;
display: inline-block;
margin-bottom: 0;
padding: 8px 15px 5px;
cursor: pointer;
touch-action: manipulation;
}
.c-cb > label::before {
content: "";
position: absolute;
border: 2px solid currentcolor;
background: transparent;
}
.c-cb > input[type="checkbox"]:focus + label::before {
border-width: 4px;
outline: 3px dashed #228bec;
}
.c-cb > label::after {
box-sizing: content-box;
content: "";
position: absolute;
top: 11px;
left: 9px;
width: 18px;
height: 7px;
transform: rotate(-45deg);
border: solid;
border-width: 0 0 5px 5px;
border-top-color: transparent;
opacity: 0;
background: transparent;
}
.c-cb > input[type="checkbox"]:checked + label::after {
opacity: 1;
}
Mit unserem gestylten Markup sieht jetzt alles besser aus:
Der bisherige Code
Git
Um den Stand des Codes zu sehen, wie er am Ende dieses Artikels sein sollte, greifen Sie auf Ihre Kopie unseres Repos zu:
cd mdn-svelte-tutorial/03-adding-dynamic-behavior
Oder laden Sie direkt den Inhalt des Ordners herunter:
npx degit opensas/mdn-svelte-tutorial/03-adding-dynamic-behavior
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/c862d964d48d473ca63ab91709a0a5a0?version=3.23.2