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
Diceist 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
rollDicewird definiert - sie gibt eine Zufallszahl zwischen 1 und 6 zurück. - Zeile 8
rollDicewird aufgerufen und das Ergebnis in der Konstantenumgespeichert.- Zeile 12
- Die Konstante
numwird 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
useStateverwendet.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.useStateist ein sog. Hook. Ein Hook ist eine Funktion, die es ermöglicht, den Zustand einer Komponente zu speichern und zu aktualisieren.useStategibt 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).
numenthält immer den aktuellen Wert. Dass es einconstist, 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 FunktionsetNumaufgerufen, 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
dicewird 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 auftruegesetzt wird und nach 1 Sekunde wieder auffalse. FallsisRollingtrueist, wird eine CSS-KlasseisRollingzumdiv-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
isRollingwird hinzugefügt. Beim Klicken auf den Würfel wird in Zeile 27isRollingauftruegesetzt.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. WennisRollingauftruegesetzt wird, wird ein Intervall gestartet, das nach 1 SekundeisRollingwieder auffalsesetzt.- 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 Komponenteunmountedwird").- Hier wird mit
return () => clearInterval(interval)der Intervalltimer gestoppt, sofern er noch nicht abgelaufen ist. - Der Rückgabe-Wert eines
- Zeile 24
- Falls
isRollingtrueist, wird die CSS-KlasseisRollingzumdiv-Element hinzugefügt. Damit nicht von Hand einzelne CSS-Klassen hinzugefügt werden müssen, wird die Bibliothekclsxverwendet.clsxsortiertefalseyWerte 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
DiceComponentausgelagert. Diese erhält die PropsonRoll,isRollingundnum. Da TypeScript verwendet wird, werden diese Props auf Zeilen 9-13 im InterfacePropsdefiniert.- Auf Zeile 10 steht:
num: number-nummuss übergeben werden, bspw.<DiceComponent num={3} />.- Die Syntax
onRoll?: () => voidbedeutet, dass die ProponRolloptional ist - das?beschreibt dies. - Auf Zeile 10 steht:
- Zeile 29
- Es wird ein neuer Zustand
historyhinzugefü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
numder History hinzugefügt.setHistory((prev) => [num, ...prev].slice(0, 5))macht mehrere Dinge:previst der vorherige Zustand vonhistory, also die letzten Würfen (kann auch leer sein).[num, ...prev]erstellt ein neues Array mit dem aktuellen Wertnumals 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-Klassehistoryerstellt. 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} />;
})}historyist ein Array mit den letzten 5 Würfen. Mit der Methodemapwird über das Array iteriert und für jeden Wertvaleine neueDiceComponenterstellt. Der Indexidxwird alskey-Prop übergeben.keyist ein spezielles Attribut in React, das verwendet wird, um die einzelnen Elemente in einer Liste zu identifizieren. Es ist wichtig, dass jederkeyinnerhalb 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.Fragmentgepackt: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);
}
}