Dieselbe App in React
Das Kapitel Frameworks im Vergleich hat Vue, React und Angular einander gegenübergestellt. Diese Seite zeigt den Katalog von Lorraine Commons mit Suche und Favoriten in React, so wie ihn die Teile 3 bis 5 in Vue gebaut haben. In React ist eine Komponente eine Funktion, die JSX zurückgibt, eine Schreibweise, die wie HTML aussieht und doch JavaScript ist.
In deinem Projekt
Abschnitt betitelt „In deinem Projekt“Das Framework deines Projekts wählst du laut Projektauftrag selbst. React lohnt sich, wenn du es im Beruf brauchst oder schon kennst. Komponenten, Props, Zustand, abgeleitete Werte und Routing kennst du aus dem Kurs, nur die Namen und einige Regeln ändern sich. Die Tabelle im Abschnitt Übertragbarkeit ordnet die Begriffe einander zu.
Für ein neues Projekt empfiehlt react.dev ein Framework wie Next.js oder React Router. Eine SPA ohne eigenen Server baust du auch mit Vite, so wie das Beispiel. Einige Regeln sind in React anders als in Vue. Den Zustand änderst du nie direkt, sondern setzt mit der Funktion von useState einen neuen Wert, bei einem Array eine Kopie. Hooks wie useState rufst du nur auf der obersten Ebene der Komponente auf und nie in einer Bedingung, und useEffect brauchst du nur, um die Komponente mit etwas ausserhalb von React abzugleichen.
Ein kleines Beispiel
Abschnitt betitelt „Ein kleines Beispiel“Das Beispiel liegt unter optional/react/de und läuft mit Vite. useState gibt den aktuellen Wert und eine Funktion zurück, die einen neuen Wert setzt. Danach führt React die ganze Funktion App erneut aus und zeichnet, was sich geändert hat. Ein abgeleiteter Wert wie die Treffer der Suche braucht deshalb kein computed, er wird bei jedem Durchlauf einfach berechnet. Das Suchfeld ist kontrolliert, sein Wert kommt aus dem Zustand, und onChange meldet jede Eingabe.
import { useState } from 'react'
import items from '../data/items.json'import { search } from './catalog.js'import ItemCard from './ItemCard.jsx'
export default function App() { // useState returns the current value and a function that sets a new one and draws again const [query, setQuery] = useState('') const [favorites, setFavorites] = useState([])
// A derived value is simply computed while drawing, where Vue uses computed const hits = search(items, query)
// The state lives here, the card only reports the click through its callback prop function toggleFavorite(id) { setFavorites((current) => (current.includes(id) ? current.filter((favorite) => favorite !== id) : [...current, id])) }
return ( <main> <h1>Katalog</h1> <label htmlFor="search">Suchen</label> <input id="search" type="search" value={query} onChange={(event) => setQuery(event.target.value)} /> <p role="status"> {hits.length} Dinge gefunden, {favorites.length} Favoriten </p> <ul className="grid"> {hits.map((item) => ( <li key={item.id}> <ItemCard item={item} favorite={favorites.includes(item.id)} onToggleFavorite={toggleFavorite} /> </li> ))} </ul> </main> )}Die Karte bekommt ihre Props als ein Objekt. Statt ein Event auszulösen, ruft sie die Funktion auf, die ihr die Elternkomponente als Prop gegeben hat. Den Zustand der Favoriten hält so weiterhin App, wie im Kapitel Zustand am richtigen Ort.
// The props arrive as one object, a function prop takes the place of an emitted eventexport default function ItemCard({ item, favorite, onToggleFavorite }) { return ( <article className="card"> <h2>{item.name}</h2> <p> {item.category}, CHF {item.deposit} Pfand </p> <button type="button" aria-pressed={favorite} onClick={() => onToggleFavorite(item.id)}> {favorite ? 'Favorit entfernen' : 'Als Favorit merken'} </button> </article> )}Der Test rendert die App mit createRoot in ein Element von happy-dom. act sorgt dafür, dass React alle Änderungen abgeschlossen hat, bevor der Test prüft. Für grössere Projekte nimmt man meist die React Testing Library, die das Suchen von Elementen und das Tippen erleichtert.
import { act } from 'react'import { createRoot } from 'react-dom/client'import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import items from '../data/items.json'import App from '../src/App.jsx'import { search } from '../src/catalog.js'
// React checks that every update in a test runs inside act(), the async form waits for all of themglobalThis.IS_REACT_ACT_ENVIRONMENT = true
let containerlet root
beforeEach(async () => { container = document.createElement('div') document.body.append(container) root = createRoot(container) await act(async () => { root.render(<App />) })})
afterEach(async () => { await act(async () => {Offizielle Links
Abschnitt betitelt „Offizielle Links“Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.