Aufgabe 07-6a - Quiz reloaded
Lernziele
Abschnitt betitelt „Lernziele“- Du modellierst den Zustand einer kleinen Anwendung vollständig, bevor du die erste Codezeile schreibst.
- Du setzt einen Ablauf mit Phasen ausschließlich über State und bedingtes Rendern um.
- Du kapselst wiederverwendbare Logik in einem eigenen Hook und weist nach, dass er in einem zweiten Kontext unverändert arbeitet.
- Du vergleichst den deklarativen mit dem imperativen Ansatz an einer konkreten Erweiterung.
Ausgangslage
Abschnitt betitelt „Ausgangslage“Ein Quiz ist eine kleine Maschine mit klaren Zuständen: Es läuft, es zeigt Rückmeldung, es ist fertig. Wer diese Zustände vorher aufschreibt, hat das Programm im Kopf schon gebaut; wer gleich lostippt, verheddert sich in Wenn-dann-Ketten.
Du baust die erste vollständige React-Anwendung: ein Multiple-Choice-Quiz mit Phasen, Punktestand und Auswertung (siehe Kapitel React-Grundlagen, alle Einheiten, besonders 7.4 bis 7.6). Die Übung führt zusammen, was die React-Wochen aufgebaut haben, und ergänzt die Hook-Familie um ein selbstgebautes Mitglied. Sie passt nach Einheit 7.6 (Die Hook-Familie).
Allgemein
Abschnitt betitelt „Allgemein“Projektordner anlegen siehe https://qiz.at/projektordner
-
Lies vorher kurz https://qiz.at/assignmentsabgabe durch.
-
Nur vollständige Abgaben (siehe https://qiz.at/rules) können positiv bewertet werden.
-
Abgabe- und Testdetails siehe https://qiz.at/assignmentsabgabe
-
Die Übung liegt als Unterordner
uebung-07-6ain deinem Jahres-RepositorySJ26_27_4AHITM_SEW_<Benutzername>_exercises, angelegt wieuebung-07-1amitnpm create vite@latest uebung-07-6a -- --template react-ts. Du arbeitest allein. -
Leg
src/components/Quiz.tsxan und render die Komponente ausApp.tsx. Bis die Fragendatei steht, genügt eine kurze Platzhalterliste direkt inApp.tsx:import { Quiz } from "./components/Quiz";// import { questions } from "./data/questions"; // wire this up laterconst questions = [{ text: "2 + 2 = ?", answers: ["3", "4", "5"], correctIndex: 1 },];export default function App() {return (<main><Quiz questions={questions} /></main>);} -
Verbindliche Regel: kein direkter DOM-Zugriff (
document.ist in den Komponenten verboten); jede Ansicht folgt aus Phase und State über bedingtes Rendern. Schriftliche Antworten stehen als Kommentar im Code oder in einer Markdown-Datei im Projekt.npm run buildmuss fehlerfrei durchlaufen;node_modules/kommt nicht ins Repository. -
Für die Stufe PlusPlus brauchst du die imperative Vanilla-JavaScript-Fassung desselben Quiz:
quiz-vanilla.zipmitquiz-vanilla.htmlundquiz-vanilla.js. Entpack das Archiv so, dass beide Dateien im selben Ordner liegen; danach genügt ein Doppelklick auf die HTML-Datei, es braucht keinen Server.
Wesentlich_überwiegend
Abschnitt betitelt „Wesentlich_überwiegend“Aufgabe
Abschnitt betitelt „Aufgabe“- Zustandsmodell zuerst: Bestimm vor der ersten Codezeile die State-Variablen des Quiz (Kandidaten: Nummer der aktuellen Frage, Punktestand, gewählte Antwort, Phase als Union Type
"running" | "feedback" | "finished"). Prüf jede gegen die Regel aus Aufgabe 07-4a: echter State oder ableitbar? Halte das Ergebnis als Tabelle inSTATE_MODEL.mdfest. - Skizzier den Ablauf als Zustandsdiagramm: Phasen als Zustände, Benutzeraktionen als Übergänge (Antwort geklickt, Weiter, Nochmal). Die Skizze ist Teil der Abgabe.
- Leg
interface Question(Text, drei Antworten, Index der richtigen) und mindestens acht Fragen insrc/data/questions.tsan und ersetz die Platzhalterliste. - Ablauf: Eine Frage mit drei Antwort-Buttons; nach dem Klick Phase
feedback(richtige Antwort grün, falsch gewählte rot, alle Buttons deaktiviert), Button “Weiter”; nach der letzten Frage Phasefinishedmit “n von 8 richtig” und “Nochmal”. - Eine Fortschrittszeile “Frage 3 von 8 - Punkte: 2” ist immer aktuell, ohne dass irgendwo “aktualisiert” wird. Halte als Kommentar fest, warum das im deklarativen Modell automatisch gilt.
Tipp: Zwei Booleans
isFinishedundshowFeedbackergeben vier Kombinationen, von denen nur drei Sinn ergeben; der Union Type lässt den unmöglichen Zustand gar nicht erst zu. “Objects are not valid as a React child” heißt: Du renderst ein ganzes Objekt statt eines Felds, etwa{question}statt{question.text}. Der Punktestand hinkt hinterher? Dann fehlt die Funktionsform des Setters.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
npm run buildohne TypeScript-Fehler durchläuft und das Quiz von der ersten bis zur letzten Frage und über “Nochmal” durchspielbar ist.- die Phase ein Union Type ist und es keine zwei Booleans für denselben Sachverhalt gibt.
- die Fortschrittszeile abgeleitet ist und kein
document.-Zugriff vorkommt; Buttons werden überdisabledundclassNameaus dem State gesteuert. interface Questiontypisiert ist, mindestens acht Fragen insrc/data/questions.tsliegen und Zustandstabelle und Zustandsdiagramm zur Umsetzung passen.
Wie unter Allgemein, dazu ein Foto des Zustandsdiagramms.
Wesentlich_vollständig
Abschnitt betitelt „Wesentlich_vollständig“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieses Projekts (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Die Auswertung zeigt zusätzlich den Prozentwert und einen Kommentar zur Leistung (etwa ab 80 Prozent “Sehr gut!”); beides ist abgeleitet.
- Der beste Prozentwert soll ein Neuladen der Seite überleben: Schreib den Hook
useLocalStorage(key, initial)nach dem Muster aus Einheit 7.6 insrc/hooks/useLocalStorage.tsund verwende ihn für ein “Best: n %” in der Auswertung. - Der Hook ist quizfrei formuliert (kein Quiz-Begriff im Hook) und muss unverändert in einer anderen Anwendung funktionieren.
- Weis nach, dass der Bestwert wirklich gespeichert wird: einmal durchspielen, Seite neu laden, den Eintrag in den DevTools unter Application - Local Storage prüfen. Screenshot in die Abgabe.
Tipp:
localStoragespeichert nur Text; Zahlen kommen als String zurück (Number(...)beim Lesen,String(...)beim Schreiben). Mit der Lazy-FormuseState(() => localStorage.getItem(key) ?? initial)liest der Hook den Speicher nur beim ersten Rendern. Prozentwert:Math.round((score / questions.length) * 100).
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- Prozentwert und Kommentar abgeleitet sind und keinen eigenen State haben.
useLocalStoragein einer eigenen Datei liegt, keinen Quiz-Begriff enthält und die Lazy-Form verwendet.- der Bestwert das Neuladen überlebt und der Screenshot das belegt.
Wie unter Allgemein, dazu der Local-Storage-Screenshot.
PlusPlus Aufgabe
Abschnitt betitelt „PlusPlus Aufgabe“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieses Projekts (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Bring die ausgeteilte imperative Vanilla-Fassung (siehe Allgemein) lokal zum Laufen und lies den Code.
- Bau in beide Fassungen dieselbe kleine Erweiterung ein: Nach einer falschen Antwort zeigt eine Zeile “davon richtig: k von n” (richtige Antworten von bisher beantworteten). Protokollier je Fassung, welche Stellen du anfassen musstest.
- Schreib den Vergleich (fünf bis acht Sätze): Wo steckt im imperativen Ansatz die meiste Arbeit? Welche Fehlerklasse verschwindet deklarativ? Und ehrlich: Gibt es etwas, das imperativ einfacher war?
Tipp: Such in der Vanilla-Fassung nach allen Stellen, an denen
progressElement.textContentgesetzt wird. Genau diese Vervielfachung ist der Punkt der Aufgabe.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- beide Fassungen laufen und die Erweiterung in beiden funktioniert.
- das Protokoll konkrete Stellen je Fassung nennt.
- der Vergleich die Fehlerklasse benennt und auch auf Vorteile des imperativen Ansatzes eingeht.
Wie unter Allgemein; die erweiterte Vanilla-Fassung liegt im Unterordner vanilla/.
MandalorianPlus Aufgabe
Abschnitt betitelt „MandalorianPlus Aufgabe“Für alle, die in der Einheit mit den Schritten oben fertig sind bzw. optional (= freiwillig), wenn du in der Einheit nicht fertig wirst.
Aufgabe
Abschnitt betitelt „Aufgabe“Erweitere (siehe Erweitern in https://qiz.at/rules) den vollständigen Teil der vorherigen Stufe dieses Projekts (ändere oder lösche nur, was für diese Aufgabe unbedingt notwendig ist):
- Die Antworten jeder Frage erscheinen in zufälliger Reihenfolge. Begründe als Kommentar, warum die Mischung nicht beim Rendern berechnet werden darf und wo sie stattdessen wohnt, und setz das um. Die Farben der Rückmeldung müssen weiter an der richtigen Antwort hängen.
- Beweis die Wiederverwendbarkeit deines Hooks: Eine neue Komponente
PlayerNamemerkt sich mit unverändertemuseLocalStorageeinen Spielernamen über ein kontrolliertes Eingabefeld; der Name überlebt das Neuladen.
Tipp: Was passiert mit einem
Math.random()im Rumpf der Komponente, wenn nach dem Klick auf eine Antwort neu gerendert wird? Und was machtStrictModeim Entwicklungsmodus mit jedem Rendern? Alskeyder Buttons eignet sich der Antworttext, nicht die Position.
Bewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Vollständig, wenn:
- die Reihenfolge pro Frage gemischt ist, während der Feedback-Phase stabil bleibt und die Färbung zur richtigen Antwort passt.
- die Begründung zur Mischung den Render-Kreislauf benennt.
PlayerNameden Hook ohne Änderung verwendet und der Name das Neuladen überlebt.
Wie unter Allgemein.
Wissenscheck
Abschnitt betitelt „Wissenscheck“- Warum ist die Phase als Union Type besser modelliert als mit zwei Booleans (
isFinished,showFeedback)? - Welche Werte des Quiz sind abgeleitet und dürfen keinen eigenen State haben?
- Warum sind die Antwort-Buttons in der Feedback-Phase deaktiviert, und wie erreicht man das deklarativ?
- Was macht
useLocalStoragezu einem Hook und nicht zu einer gewöhnlichen Funktion? - Welche Fehlerklasse des imperativen Ansatzes verhindert React strukturell?
Zeit dafür
Abschnitt betitelt „Zeit dafür“Durchschnittlich bis Wesentlich_vollständig: ca. 100 min
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA