Würfeln
-
Neues Dokument anlegen, in welchem wir den Würfel später verwenden.:
docs/tdev/dice/index.mdx# Würfeln
-
Den Würfel erstellen. In React sind alle Komponenten eine Funktion, deren Rückgabewert in einer erweiterten
HTML
-Syntax geschrieben ist. Diese Syntax wird von React in HTML umgewandelt.docs/tdev/dice/Dice.tsximport React from 'react';
const Dice = () => {
return (
<div>
Würfel
</div>
);
}
export default Dice;Die Funktion
Dice
ist eine React-Komponente und gibt eindiv
-Element mit dem Text "Würfel" zurück. Die Komponente kann nun als<Dice />
aufgerufen werden. -
Die Komponente in der
index.mdx
-Datei importieren und verwenden:docs/tdev/dice/index.mdx---
page_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
---
import Dice from './Dice';
# Würfeln
<Dice />Leerzeile nach dem ImportBeachte, dass nach dem Import-Statement auf auf Zeile 5 eine Leerzeile stehen muss, damit der Import funktioniert und die Überschrift korrekt dargestellt wird.
Die Vorschau sollte nun den Text "Würfel" anzeigen.
-
Nun soll anstelle des "Würfel"-Textes eine Zahl zwischen 1 und 6 angezeigt werden.
docs/tdev/dice/Dice.tsximport React from 'react';
const rollDice = () => {
return Math.floor(Math.random() * 6) + 1;
}
const Dice = () => {
const num = rollDice();
return (
<div>
{num}
</div>
);
};
export default Dice;- Zeilen 3-5
- Die Funktion
rollDice
wird definiert - sie gibt eine Zufallszahl zwischen 1 und 6 zurück. - Zeile 8
rollDice
wird aufgerufen und das Ergebnis in der Konstantenum
gespeichert.- Zeile 12
- Die Konstante
num
wird in geschweifte Klammern{num}
gesetzt. Somit wird der Inhalt evaluiert/ausgewertet und das Ergebnis angezeigt.
ZufallszahlJedesmal beim Neuladen der Seite (oder wenn beim "Hinnavigieren" auf die Seite) wird eine neue Zufallszahl generiert. In React-Sprache sagt man, "wenn die Komponente neu gerendert wird, wird eine neue Zufallszahl generiert".
-
Beim Klicken auf den Würfel soll nun eine neue Zahl angezeigt werden. Dazu muss
- Ein neuer Wert generiert werden
- der Aktuelle Wert durch den neuen Wert ersetzt werden.
Hier kommt React ins Spiel. React hat einen eigenen Mechanismus, um den aktuellen Wert zu speichern und bei Änderungen zu aktualisieren. Dazu wird der Hook
useState
verwendet.docs/tdev/dice/Dice.tsximport React from 'react';
const rollDice = () => {
return Math.floor(Math.random() * 6) + 1;
}
const Dice = () => {
const [num, setNum] = React.useState(rollDice());
return (
<div onClick={() => setNum(rollDice())}>
{num}
</div>
);
};
export default Dice;- Zeile 8
React.useState
ist ein sog. Hook. Ein Hook ist eine Funktion, die es ermöglicht, den Zustand einer Komponente zu speichern und zu aktualisieren.useState
gibt ein Array mit zwei Werten zurück:- den aktuellen Wert (in diesem Fall
num
) - eine Funktion, um den Wert zu aktualisieren (in diesem Fall
setNum
).
num
enthält immer den aktuellen Wert. Dass es einconst
ist, ist kein Widerspruch, da alle Werte in einer React-Komponente nur genau bei einem Render verwendet werden. Dank demuseState
-Hook wird der Wert von einem zum nächsten Render übertragen.- Zeile 11
- im
onClick
-Event wird die FunktionsetNum
aufgerufen, um den aktuellen Wert zu aktualisieren.
AusprobierenBeim Klicken auf den Würfel wird nun eine neue Zahl angezeigt.
-
Aufhübschen mit CSS
Wir fügen ein Stylesheet hinzu, um den Würfel zu stylen. Damit es keine Konflikte zwischen den verschiedenen Stylesheets gibt, werden sog. CSS-Module verwendet. Ein CSS-Modul bezieht sich nur auf die Komponenten, in welchen es importiert wird. Es hat die Dateiendung.module.css
.docs/tdev/dice/Dice.module.css.dice {
width: 30px;
height: 30px;
border: 1px solid red;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
}und in der
Dice.tsx
-Datei importieren:docs/tdev/dice/Dice.tsximport React from 'react';
import styles from './Dice.module.css';
const rollDice = () => {
return Math.floor(Math.random() * 6) + 1;
};
const Dice = () => {
const [num, setNum] = React.useState(rollDice());
return (
<div className={styles.dice} onClick={() => setNum(rollDice())}>
{num}
</div>
);
};
export default Dice;- Zeile 2
- Die CSS-Datei wird als JS-Objekt importiert.
- Zeile 12
- Die CSS-Klasse
dice
wird mitclassName={styles.dice}
auf dasdiv
-Element angewendet.
Jetzt mit Style 😎
-
Eine Animation fürs Würfeln Hhinzufügen Dass der Würfel am würfeln ist, soll visualisiert werden.
1Dazu brauchen wir einen zusätzlichen Zustand
isRolling
, der beim Würfeln auftrue
gesetzt wird und nach 1 Sekunde wieder auffalse
. FallsisRolling
true
ist, wird eine CSS-KlasseisRolling
zumdiv
-Element hinzugefügt.docs/tdev/dice/Dice.tsximport React from 'react';
import styles from './Dice.module.css';
import clsx from 'clsx';
const rollDice = () => {
return Math.floor(Math.random() * 6) + 1;
};
const Dice = () => {
const [num, setNum] = React.useState(rollDice());
const [isRolling, setIsRolling] = React.useState(false);
React.useEffect(() => {
if (isRolling) {
const interval = setInterval(() => {
setIsRolling(false);
}, 1000);
return () => clearInterval(interval);
}
}, [isRolling]);
return (
<div
className={clsx(styles.dice, isRolling && styles.isRolling)}
onClick={() => {
setNum(rollDice());
setIsRolling(true);
}}
>
{num}
</div>
);
};
export default Dice;- Zeile 11
- Ein neuer Zustand
isRolling
wird hinzugefügt. Beim Klicken auf den Würfel wird in Zeile 27isRolling
auftrue
gesetzt.Zeile 27: Arrow-FunctionBeachte die geänderte Syntax auf Zeile 27:
Der Funktionskörper ist nun mehrzeilig und braucht deshalb geschweifte Klammern.
AusonClick={() => setNum(rollDice())}
wird:onClick={() => {
setNum(rollDice());
setIsRolling(true);
}} - Zeilen 14-20
- Der
useEffect
-Hook wird verwendet, um eine Funktion auszuführen, wenn sich der ZustandisRolling
ändert. WennisRolling
auftrue
gesetzt wird, wird ein Intervall gestartet, das nach 1 SekundeisRolling
wieder auffalse
setzt.- Der Rückgabe-Wert eines
useEffect
-Hooks kann zum Stoppen des Intervall-Timers verwendet werden. Ist der Rückgabewert eine Funktion, so wird diese aufgerufen, sobald sichisRolling
ändert oder von der Seite wegnavigiert wird (in React-Sprache: "sobald die Komponenteunmounted
wird").- Hier wird mit
return () => clearInterval(interval)
der Intervalltimer gestoppt, sofern er noch nicht abgelaufen ist. - Der Rückgabe-Wert eines
- Zeile 24
- Falls
isRolling
true
ist, wird die CSS-KlasseisRolling
zumdiv
-Element hinzugefügt. Damit nicht von Hand einzelne CSS-Klassen hinzugefügt werden müssen, wird die Bibliothekclsx
verwendet.clsx
sortiertefalsey
Werte aus und gibt nur die aktuell aktiven CSS-Klassen zurück.- So wäre bei
clsx('foo', false, 1 > 0 && 'bar', 0 > 1 && 'baz')
der Rückgabewert'foo bar'
. - So wäre bei
Damit der Würfel auch wirklich animiert ist, muss noch das CSS angepasst werden.
docs/tdev/dice/Dice.module.css.dice {
width: 30px;
height: 30px;
border: 1px solid red;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
&.isRolling {
animation: rotate 1s ease-out infinite;
}
}
@keyframes rotate {
100% {
transform: rotate(2turn) scale(1.2);
}
} -
(Optional) Eine History hinzufügen und die letzten 5 Würfe anzeigen:
3docs/tdev/dice/Dice.tsximport React from 'react';
import styles from './Dice.module.css';
import clsx from 'clsx';
const rollDice = () => {
return Math.floor(Math.random() * 6) + 1;
};
interface Props {
num: number;
onRoll?: () => void;
isRolling?: boolean;
}
const DiceComponent = (props: Props) => {
return (
<div
className={clsx(styles.dice, props.isRolling && styles.isRolling)}
onClick={props.onRoll}
>
{props.num}
</div>
);
};
const Dice = () => {
const [num, setNum] = React.useState(rollDice());
const [isRolling, setIsRolling] = React.useState(false);
const [history, setHistory] = React.useState<number[]>([]);
React.useEffect(() => {
if (isRolling) {
const interval = setInterval(() => {
setIsRolling(false);
setHistory((prev) => [num, ...prev].slice(0, 5));
}, 1000);
return () => clearInterval(interval);
}
}, [isRolling, num]);
return (
<div className={clsx(styles.container)}>
<DiceComponent
num={num}
isRolling={isRolling}
onRoll={() => {
setNum(rollDice());
setIsRolling(true);
}}
/>
<div className={clsx(styles.history)}>
{history.map((val, idx) => {
return <DiceComponent num={val} key={idx} />;
})}
</div>
</div>
);
};
export default Dice;- Zeilen 15-24
- Ein einzelner Würfel wird in eine eigene Komponente
DiceComponent
ausgelagert. Diese erhält die PropsonRoll
,isRolling
undnum
. Da TypeScript verwendet wird, werden diese Props auf Zeilen 9-13 im InterfaceProps
definiert.- Auf Zeile 10 steht:
num: number
-num
muss übergeben werden, bspw.<DiceComponent num={3} />
.- Die Syntax
onRoll?: () => void
bedeutet, dass die ProponRoll
optional ist - das?
beschreibt dies. - Auf Zeile 10 steht:
- Zeile 29
- Es wird ein neuer Zustand
history
hinzugefügt, der die letzten 5 Würfe speichert. Achtung! React wechselt den Zustand nur dann, wenn sich die Referenz ändert. - Zeile 35
- Nachdem die Animation abgelaufen ist, wird der aktuelle Wert
num
der History hinzugefügt.setHistory((prev) => [num, ...prev].slice(0, 5))
macht mehrere Dinge:prev
ist der vorherige Zustand vonhistory
, also die letzten Würfen (kann auch leer sein).[num, ...prev]
erstellt ein neues Array mit dem aktuellen Wertnum
als ersten Wert und allen vorherigen Werten danach.prev = [1, 2, 3, 4, 5]
num = 6
[num, ...prev] // => [6, 1, 2, 3, 4, 5]slice(0, 5)
schneidet das Array auf die ersten 5 Werte. Wenn weniger als 5 Werte vorhanden sind, wird das Array nicht verändert.
- Zeilen 42-56
- Die History wird angezeigt.
Es wird ein
div
-Element mit der CSS-Klassehistory
erstellt. Darin wird zuerste eine einzelne Würfelkomponente (Zeilen 43-50) angezeigt und danach die letzten 5 Würfe (Zeilen 51-55).Bemerkenswert sind die Zeilen 52-54:
{history.map((val, idx) => {
return <DiceComponent num={val} key={idx} />;
})}history
ist ein Array mit den letzten 5 Würfen. Mit der Methodemap
wird über das Array iteriert und für jeden Wertval
eine neueDiceComponent
erstellt. Der Indexidx
wird alskey
-Prop übergeben.key
ist ein spezielles Attribut in React, das verwendet wird, um die einzelnen Elemente in einer Liste zu identifizieren. Es ist wichtig, dass jederkey
innerhalb seines Eltern-Elements eindeutig ist, damit React die Elemente effizient aktualisieren kann.Reacts Rükgabewerte
React-Komponenten dürfen immer nur ein Element zurückgeben.
return (
<div>...</div>
<div>...</div>
);wäre also nicht erlaubt.
Entweder werden die beiden
div
-Elemente in ein weiteresdiv
-Element gepackt:return (
<div>
<div>...</div>
<div>...</div>
</div>
);oder die
div
-Elemente werden in einReact.Fragment
gepackt:return (
<>
<div>...</div>
<div>...</div>
</>
);
Das Stylesheet muss ebenfalls angepasst werden, so dass die Würfel in der History farblich anders dargestellt werden.
docs/tdev/dice/Dice.module.css.dice {
width: 30px;
height: 30px;
border: 1px solid red;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
&.isRolling {
animation: rotate 1s ease-out infinite;
}
}
.container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.history {
display: flex;
gap: 5px;
margin: 10px 0;
flex-wrap: wrap;
.dice {
border-color: gray;
}
}
}
@keyframes rotate {
100% {
transform: rotate(2turn) scale(1.2);
}
}