Setup mit Vite:
// Projekt-Ordner anlegen
mkdir my-project
// Unterornder für Front- und Backend anlegen
cd my-project
mkdir frontend
mkdir backend
// react-projekt mit vite initialisieren
npm create vite@latest frontend
// -> Installer-Anweisungen folgen
// dependencies installieren
cd frontend
npm install
// dev srever starten
npm run dev
// projekt bauen und buil previewen
npm run build
nmp run previewweitere mögliche Schritte im Projekt:
- Tailwind installieren
- Ablage-Ort
dist/ins backend verschieben
Mehr Infos dazu in meinem Vite-Artikel.
Komponenten:
- Namen werden PascalCase geschrieben
- müssen genau 1 Element zurückgeben (dieses Element kann mehrere Kind-Elemente enthalten) => <></> (= Fragment)
- Best-Practice: Ablage jeweils in einer eigenen .jsx, die identisch zur Komponente benannt ist
JSX:
REACT verwendet JSX, das ist Javascript, das wie HTML aussieht. Es gibt einige Unterschiede zu echtem HTML:
- Styling von Elementen über „className=‘ ‚ „, statt über „class=‘ ‚ „
- in <label> muss „htmlFor“ statt „for“ genommen werden
- in <input> muss „defaultValue“ statt „value“ verwendet werden
- jeder return darf nur 1 Element zurückgeben !=> Inhalt wrappen in <div></div> oder <></>
- JS-Code/Template Literals müssen in {} stehen
- default-Einheit für Zahlen ist px, wenn kenn string mit anderer Einheit angegeben ist
- inline-CSS sollte vermieden werden
Patterns für Bedingungen:
- if…return… => für early exits oder klare mehrzeilige Blöcke
- Bedingung ? A : B => schnelles tooglen
- Bedingung ?? (<div>…</div>) => div nur zeigen wenn Bedingung true
- switch() case => für mehr als zwei Optionen
Anlegen von Komponenten:
// Deklaration, z.B. in ./src/components/Header.jsx
const Header = () => (<header>...some content...</header>)
// oder (don't do):
function Header() {
return (<header>...some content...</header>)
}// Verwendung, z.B. in ./App.jsx
import Header from "./Header"
//oder (better):
import Header from "./Header.jsx"
const App = ()=> (
<div id="wrapper">
<Header />
...
</div>
)Styling von Komponenten: (Verwendung von className)
// CSS-Klassen
import "./style.css". // importiert .css direkt in js-file
const NavBar = () => (<nav className="cusom-class tailwind-class">...</nav>)
// inline CSS (don't do!)
const NavBar = () => {
return <nav style={{background:"red", border:"1px solid green"}}>...</nav>
}Übergabe von Properties:
// Deklaration der Komponente in PageHeading.jsx
const PageHeading = (props) => (
<>
<h1>{props.title}</h1>
<h3>Author: {props.name}</h3>
</>
)
// oder destructured:
const PageHeading = ({title,name}) => (
<>
<h1>{title}</h1>
<h3>Author: {name}</h3>
</>
)// Verwendung der Komponente, z.B. in App.jsx
import PageHeading from "./componets/PageHeading"
const book = {
title: "Harry Potter",
author: "J.K.Rowling"
}
...
<PageHeading {...book}/>
...
// oder:
...
<PageHeading title=book.title name=book.author/>
...
// oder:
// (ABER! = never do):
// hierbei wird nur die Referenz übergeben
// changes in data -> changes original book
...
<PageHeading data={book}/>
...Daten können über Properties nur an Kind-Elemente übergeben werden, nicht in die andere Richtung.
Rendern von Listen:
// Erstellen eine bsp-Objektes mit Daten
const starters = [
{ id: 1,
name: 'Bulbasaur',
sprite:'https://.../sprites/pokemon/1.png',
type: 'Grass · Poison',
},
{ id: 4,
name: 'Charmander',
sprite:'https://.../sprites/pokemon/4.png',
type: 'Fire',
},
{ id: 7,
name: 'Squirtle',
sprite:'https://../sprites/pokemon/7.png',
type: 'Water',
},
];
// Deklarieren einzelner Komopnenten
const PokeCard = ({ poke }) => (
<article>
<img src={poke.sprite} alt={poke.name} />
<h4>{poke.name}</h4>
<p>{poke.type}</p>
</article>
);
const PokeGrid = ({ pokemon }) => (
<section>
{pokemon.map((p) => (
<PokeCard key={p.id} poke={p} />
))}
</section>
);
// Verwendung der Komponenten
const App = () => (
<main>
<h3>
First-generation Starters
</h3>
{/* List rendered via .map() */}
<PokeGrid pokemon={starters} />
{/* Quick example: show only water types */}
<h3>Water squad</h3>
{starters
.filter((p) => p.type.includes('Water'))
.map((p) => (
<span key={p.id}>
{p.name}
</span>
))}
</main>
);Jedes Listen-Element benötigt einen eindeutigen key, damit react die Elemente zuordnen kann.
Events handeln:
// Definieren der Handler
const App = () => {
const handleClick = (e) => {
console.log('Clicked — SyntheticEvent:', e);
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
alert(`Pressed Enter: "${e.target.value}"`);
}
};
const handleSubmit = (e) => {
e.preventDefault(); // stop page reload
const input = e.target.elements.message.value;
alert(`Form submitted 🎉: ${input}`);
};
// Verknüpfung der Handler
return (
<main>
<div>
<button onClick={handleClick}>
Click me
</button>
<input
type="text"
placeholder="Type and press Enter"
onKeyDown={handleKeyDown}/>
<form onSubmit={handleSubmit}>
<input
name="message"
type="text"
placeholder="Type, hit Enter or Btn"/>
<button>Submit form</button>
</form>
</div>
</main>
);
};Events werden über camelCase-Bezeichner dem JSX-Element hinzugefügt. REACT führt die referenzierte Funktion in einem syntetischen Event auf.
Hooks:
useState()
// Syntax:
const [value, setterFncForVal] = useState(initialValue)
// Ändern des Wertes
setterFncForVal(newValue) // direkte Zuweisung neuer Wertes (nur wenn vorheriger Zustand unrelevant nutzen)
setterFncForVal((oldValue) => oldValue +1 ) // callback mit geplanten Werten
Beispiel: Counter
import { useState } from 'react';
const App = ()=> {
const [counter, setCounter] = useState(0); // Initialwert 0
return (
<>
<button onClick={()=> setCounter((prev) => prev +1)}>+</button>
<span>{counter}</span>
<button onClick={()=> setCounter((prev) => prev -1)}>-</button>
</>
)
}
Beispiel: String ändern
// onClick
const App = ()=> {
const [message, setMessage] = useState("Hello");
return (
<>
<span>{message}</span>
<button onClick={()=> setMessage("World")}>change Message</button>
</>
)
}
// onChange
const App = ()=> {
const [text, setText] = useState("");
return (
<>
<input value={text} onChange={(e)=> setText(e.target.value)} />
<span>{text}</span>
</>
)
}
Beispiel: Checkbox
const MyCheckbox = () => {
const [liked, setLiked] = useState(true);
return (
<>
<input type="checkbox" checked={liked} onChange={(e)=>setLike(e.target.checked} />
<span>I liked this</span>
<p> You {liked ? 'liked' : 'did not like'} this. </p>
</>
)
}
Beispiel: Formular
const Form = () => {
const [form, setForm] = useState({ // Initialwert
firstName: '',
lastName: '',
email: '',
message: '',
});
const handleChange = (e) => {
setForm({...form, [e.target.name]: e.target.value}); // übernimm Daten, dann überschreib Element
};
return (
<>
<form>
<input
name="firstName"
value={form.firstName}
onChange={handleChange}
/>
<input
name="lastName"
value={form.lastName}
onChange={handleChange}
/>
<input
name="email"
value={form.email}
onChange={handleChange}
type="email"
/>
<textarea
name="message"
value={form.message}
onChange={handleChange}
/>
</form>
<p>
<strong>Preview:</strong>
</p>
<p>
{form.firstName} {form.lastName}
</p>
<p>{form.email}</p>
<p>{form.message}</p>
</div>
</>
);
};? verwendet um Komponenten (und ihre Kinder) dynamisch zu Re-Rendern, z.B. bei Änderung der verwendeten Daten oder User-Aktion
= nimmt als Argument den Initialwert (beliebiger Datentyp)
= liefert Array mit aktuellem Wert und zugehöriger Setter-Funktion (die auch das Re-Rendering der Komponente triggert)
Weitergabe des State-Wertes:
- der State-Wert kann (über props) nur nach unten weiter gegeben werden
Hinweise:
- InitialWert sollte nur über setterFnc geändert werden
- muss in der Komponente importiert werden, in der er verwendet werden soll
- bei mehrfacher Verwendung einer Komponente ist jeder useStateWert unabhängig und isoliert (bis man die Komponenten verknüpft)
// direktes Setzen des Wertes
setCounter(counter + 1);
setCounter(counter + 1);
setCounter(counter + 1);
// = 1, weil jede Zeile den gleichen, unveränderten // Wert 0 nutzt
// Setzen per CallBack-Fuunktion
setCounter((prev) => prev + 1);
setCounter((prev) => prev + 1);
setCounter((prev) => prev + 1);
// = 3, weil hier jeweils der fürs nächte Rendering // vorgemerkte Wert verwendet wird- ohne callback-Funktion steht der geänderte Wert erst nach dem nächsten Rendering zur Verfügung, d.h. nach dem abgeschlossen Durchlauf der Funktionen im Scope (Komponente)
- Objekte sollten niemals direkt mutiert werden, nur über Setter-Funktion
Nutzung mit Formularen:
- jedem Formelement ein
namegeben - jedem Formelement einen Eventhandler und Funktion übergeben
- im Handler
nameundvalueausevent-targetholen - neuen State-Wert als Objekt aus Spread-Operator von aktuellem Wert und aktualisierter Instanz erzeugen (Beim Setzen eines Objektes muss immer das komplette Objekt übergeben werden)
- Link: Controlled Inputs
- Link: Example Form
#TODO