Props: Daten von oben
Kurz gesagt: Props sind die Daten, die eine Komponente von ihrer Elternkomponente bekommt, so wie eine Funktion Parameter bekommt; die Kindkomponente ändert sie nicht, und children reicht Inhalt von außen herein.
Problem
Dreimal Brot
Der Katalog soll drei Produkte zeigen: Brot, Milch und Käse. Die Produktkarte ist aus der letzten Lektion fertig, also setzt App sie dreimal ein.
Konsole
Alle drei Karten sind Brot. Das Produkt steht in ProductCard selbst, und jede Karte zeigt es. Der andere Weg wären BreadCard, MilkCard und CheeseCard: drei Kopien derselben Komponente, und jede Änderung an der Karte müsste dreimal gemacht werden. Es fehlt ein Weg, der Karte zu sagen, welches Produkt sie zeigen soll.
Die Idee
Props: Daten von oben
Eine Komponente ist eine Funktion. Eine Funktion bekommt ihre Daten als Parameter, und eine Komponente bekommt sie als Attribute von ihrer Elternkomponente: der Komponente, die sie benutzt. Diese Attribute heißen Props (von „properties“). Die Elternkomponente schreibt sie, die Kindkomponente liest sie.
<ProductCard product={bread} /><ProductCard product={milk} />export default function ProductCard({ product }) { return <h2>{product.name}</h2>;}React sammelt alle Attribute in einem Objekt und übergibt es der Funktion: { product: bread }. Die Schreibweise ({ product }) nimmt daraus die Eigenschaft product heraus (Destructuring). Ohne die geschweiften Klammern ist der Parameter das ganze Objekt: function ProductCard(props), und dann props.product.name.
Es gibt eine Karte, und jede Verwendung hat andere Daten. Bei einer Funktion ist es genauso: eine Definition, viele Aufrufe mit unterschiedlichen Argumenten.
Sieh es dir an
Sieh, wie sie fließen
const bread = { name: 'Weißbrot', price: 1.2 };const milk = { name: 'Frischmilch', price: 1.6 };const cheese = { name: 'Weißkäse', price: 7.9 };
function ProductCard({ product, discount = 0 }) { return ( <article> <h2>{product.name}</h2> <p>Preis: {money(product.price * (1 - discount / 100))}</p> </article> );}
export default function App() { return ( <main> <h1>Brot und Milch</h1> <div> <ProductCard product={bread} /> <ProductCard product={milk} /> <ProductCard product={cheese} discount={10} /> </div> </main> );}Schritt für Schritt, in Worten
- App hält die Daten: drei Produkte. Darunter stehen drei ProductCards: dieselbe Komponente dreimal, und keine weiß schon, was sie zeigen soll.
- product={bread} übergibt das Objekt bread an die erste Karte. Die Attribute einer Komponente heißen Props.
- Die anderen beiden Karten bekommen ihr eigenes Produkt. Die dritte bekommt außerdem discount = 10. Jede Karte hat ihre eigenen Props.
- Jede Karte zeichnet sich aus ihren Props. Brot und Milch haben keinen discount bekommen, deshalb ist er standardmäßig 0. Der Käse ist um 10 % reduziert: 7,11 €.
- Versucht die Karte props.discount = 0, geht nichts nach oben: Im Entwicklungsmodus wirft React einen TypeError (das Props-Objekt ist eingefroren), und die Daten in App bleiben gleich.
- Daten fließen nur in eine Richtung: von der Elternkomponente nach unten zur Kindkomponente. Um oben etwas zu ändern, muss die Kindkomponente die Elternkomponente bitten. Das ist das Modul „Zustand“.
Jede Karte kennt nur ihre eigenen Props. Sie sieht die anderen Karten nicht und weiß nicht, woher die Daten in App kommen.
Sieh es dir an
Eine Karte, drei Produkte
Konsole
Die Produkte stehen in App, und es gibt eine ProductCard. Füge in App ein viertes Produkt hinzu (das Bild img/apples.svg gibt es) und noch ein <ProductCard … />. Die Karte fasst du nicht an. Bei 30 Produkten willst du eine Liste statt 30 Zeilen: Das ist die nächste Lektion.
Probier es aus
Finde den Fehler
Die Karte erscheint nicht, und in der Konsole steht „TypeError: Cannot read properties of undefined (reading 'toFixed')“.
Wähle die Zeile mit dem Fehler.
product="bread" übergibt den Text „bread“, nicht das Objekt. Text hat kein price, also ist product.price undefined, und money scheitert an toFixed. Das Objekt übergibst du in geschweiften Klammern: product={bread}.Die Idee
Ein Standardwert
Manche Produkte sind im Angebot. Die Karte bekommt ein zweites Prop, discount, in Prozent. Die meisten Produkte haben keinen Rabatt, und es ist nicht schön, wenn jede Zeile in App discount={0} schreiben muss. Ein fehlendes Prop ist undefined, wie ein fehlendes Argument einer Funktion. Der Preis wird zu price * (1 - undefined / 100), also NaN.
Deshalb bekommt das Prop einen Standardwert:
export default function ProductCard({ product, discount = 0 }) {Das ist ein Standardwert im JavaScript-Destructuring: Fehlt discount, ist es 0. In älterem Code siehst du ProductCard.defaultProps = { discount: 0 } und propTypes. React 19 beachtet sie bei Funktionskomponenten nicht mehr: Typen zu prüfen ist die Aufgabe von TypeScript.
Probier es aus
Ein Rabatt
App gibt dem Käse einen discount von 10 (Prozent). Sorge dafür, dass die Karte discount annimmt, standardmäßig 0, und den Preis mit Rabatt zeigt: das Brot 1,20 €, die Milch 1,60 €, der Käse 7,11 €.
Konsole
Lösung
ProductCard.jsxconst money = (x) => x.toFixed(2).replace('.', ',') + ' €';
export default function ProductCard({ product, discount = 0 }) {
return (
<article>
<h2>{product.name}</h2>
<p>Preis: {money(product.price * (1 - discount / 100))}</p>
</article>
);
}
App.jsximport ProductCard from './ProductCard';
const bread = { name: 'Weißbrot', price: 1.2 };
const milk = { name: 'Frischmilch', price: 1.6 };
const cheese = { name: 'Weißkäse', price: 7.9 };
export default function App() {
return (
<main>
<h1>Brot und Milch</h1>
<div>
<ProductCard product={bread} />
<ProductCard product={milk} />
<ProductCard product={cheese} discount={10} />
</div>
</main>
);
}
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
h1 { font-size: 22px; margin: 0 0 12px; }
main > div { display: flex; flex-wrap: wrap; gap: 12px; }
article { border: 1px solid #ddd5c4; border-radius: 8px; padding: 8px 12px; width: 140px; }
article img { width: 56px; height: 56px; }
h2 { font-size: 16px; margin: 4px 0; }
p { margin: 4px 0; }
.card { border: 1px solid #ddd5c4; border-radius: 8px; background: #fbf8f2; padding: 4px 12px; margin-top: 12px; max-width: 320px; }
button { font: inherit; margin-left: 8px; }
function ProductCard({ product, discount = 0 }) und der Preis money(product.price * (1 - discount / 100)). Ohne = 0 zeigen das Brot und die Milch „NaN €“: Die Prüfung schaut auf alle drei Preise.
Die Idee
Schreibgeschützt
Die Daten gehören dem, der sie hält. Die Karte kann ihre Props lesen, aber nicht ändern. Probier den Button „Noch eins“: Die Kindkomponente versucht, ihr eigenes qty zu erhöhen.
Konsole
Die Menge bleibt 2, und die Konsole meldet „TypeError: Cannot assign to read only property 'qty' of object“. Im Entwicklungsmodus friert React das Props-Objekt ein, genau um solchen Code zu fangen.
Selbst wenn es durchginge, wäre die Änderung nur in der Kindkomponente: App würde weiter 2 sagen und bei der nächsten Zeichnung wieder 2 übergeben. Zwei Orte mit verschiedenen Wahrheiten sind der Fehler aus „Eine Liste aus Daten“. Die Regel: Daten fließen nach unten, und sie zu ändern ist Sache der Elternkomponente. Wie eine Kindkomponente ihre Elternkomponente bittet, etwas zu ändern, ist das Modul „Zustand“.
Die Idee
Inhalt von außen
Der Shop hat Rahmen, die gleich aussehen und Verschiedenes enthalten: „Lieferung“ mit zwei Absätzen, „Rückgabe“ mit einem. Der Titel ist ein Prop. Der Inhalt ist ein ganzes Stück HTML, und es ist handlicher, es zwischen das öffnende und das schließende Tag zu schreiben, wie bei <section>.
<Card title="Lieferung"> <p>Innerhalb von 2 Werktagen.</p></Card>export default function Card({ title, children }) { return ( <section className="card"> <h2>{title}</h2> {children} </section> );}Alles zwischen <Card> und </Card> kommt im besonderen Prop children an. Card entscheidet, wohin es das setzt.
Konsole
Probier es aus
Der Rahmen
Card zeigt nur den Titel. Sorge dafür, dass es unter dem Titel auch den Inhalt zeigt, den App zwischen <Card> und </Card> schreibt.
Konsole
Lösung
Card.jsxexport default function Card({ title, children }) {
return (
<section className="card">
<h2>{title}</h2>
{children}
</section>
);
}
App.jsximport Card from './Card';
export default function App() {
return (
<main>
<h1>Der Warenkorb</h1>
<Card title="Lieferung">
<p>Innerhalb von 2 Werktagen.</p>
<p>Kostenlos ab 30 €.</p>
</Card>
<Card title="Rückgabe">
<p>Bis zu 14 Tage nach der Lieferung.</p>
</Card>
</main>
);
}
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
h1 { font-size: 22px; margin: 0 0 12px; }
main > div { display: flex; flex-wrap: wrap; gap: 12px; }
article { border: 1px solid #ddd5c4; border-radius: 8px; padding: 8px 12px; width: 140px; }
article img { width: 56px; height: 56px; }
h2 { font-size: 16px; margin: 4px 0; }
p { margin: 4px 0; }
.card { border: 1px solid #ddd5c4; border-radius: 8px; background: #fbf8f2; padding: 4px 12px; margin-top: 12px; max-width: 320px; }
button { font: inherit; margin-left: 8px; }
Card nimmt auch children an: function Card({ title, children }), und setzt es unter den Titel: {children}. Ohne die geschweiften Klammern zeigt die Seite das Wort „children“. Setzt du es in <h2>, werden die Absätze Teil der Überschrift, und sie heißt nicht mehr „Lieferung“.
Genauer, für Neugierige
Mehr als eine Stelle für Inhalt. Es gibt ein children. Hat der Rahmen auch einen unteren Teil, ist das ein weiteres Prop mit JSX: <Card title="Lieferung" footer={<a href="/delivery">Details</a>}>…</Card>, und Card setzt {footer} an die richtige Stelle.
Alle Eigenschaften auf einmal. <ProductCard {...product} /> übergibt jede Eigenschaft des Objekts als eigenes Prop: name, price, image. Das ist praktisch, aber man sieht nicht, was genau die Karte bekommt.
Nur die erste Ebene ist eingefroren. props.qty = 3 wirft einen Fehler, aber props.product.price = 2 geht still durch: Das Produktobjekt ist nicht eingefroren, es ist dasselbe Objekt wie in App. So ändert die Kindkomponente die Daten der Elternkomponente hinter deren Rücken. Tu das nicht.
Typen. React 19 prüft propTypes bei Funktionskomponenten nicht mehr und nutzt defaultProps für sie nicht. Die Typen der Props schreibt man mit TypeScript: Das kommt in der Lektion über ein echtes Projekt.
Übung
Das Schaufenster
Bau das Schaufenster von Grund auf. ProductCard nimmt product und discount (standardmäßig 0) und zeigt das Bild (alt ist der Name), den Namen als Überschrift der Ebene zwei und „Preis: …“ mit dem Rabatt. Card nimmt title und zeigt den Titel (Ebene zwei) und den Inhalt von außen. In App, unter der Überschrift: die drei Karten in einem div (der Käse mit discount 10), dann eine Card „Lieferung“ mit „Innerhalb von 2 Werktagen.“ und „Kostenlos ab 30 €.“.
Konsole
Lösung
App.jsximport ProductCard from './ProductCard';
import Card from './Card';
const bread = { name: 'Weißbrot', price: 1.2, image: 'img/bread.svg' };
const milk = { name: 'Frischmilch', price: 1.6, image: 'img/milk.svg' };
const cheese = { name: 'Weißkäse', price: 7.9, image: 'img/cheese.svg' };
export default function App() {
return (
<main>
<h1>Das Schaufenster</h1>
<div>
<ProductCard product={bread} />
<ProductCard product={milk} />
<ProductCard product={cheese} discount={10} />
</div>
<Card title="Lieferung">
<p>Innerhalb von 2 Werktagen.</p>
<p>Kostenlos ab 30 €.</p>
</Card>
</main>
);
}
ProductCard.jsxconst money = (x) => x.toFixed(2).replace('.', ',') + ' €';
export default function ProductCard({ product, discount = 0 }) {
return (
<article>
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p>Preis: {money(product.price * (1 - discount / 100))}</p>
</article>
);
}
Card.jsxexport default function Card({ title, children }) {
return (
<section className="card">
<h2>{title}</h2>
{children}
</section>
);
}
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
h1 { font-size: 22px; margin: 0 0 12px; }
main > div { display: flex; flex-wrap: wrap; gap: 12px; }
article { border: 1px solid #ddd5c4; border-radius: 8px; padding: 8px 12px; width: 140px; }
article img { width: 56px; height: 56px; }
h2 { font-size: 16px; margin: 4px 0; }
p { margin: 4px 0; }
.card { border: 1px solid #ddd5c4; border-radius: 8px; background: #fbf8f2; padding: 4px 12px; margin-top: 12px; max-width: 320px; }
button { font: inherit; margin-left: 8px; }
ProductCard: function ProductCard({ product, discount = 0 }), <img src={product.image} alt={product.name} />, <h2>{product.name}</h2> und der Preis mit dem Rabatt. Card: function Card({ title, children }) mit <section className="card">, <h2>{title}</h2> und {children}. App: <ProductCard product={cheese} discount={10} /> und der Inhalt zwischen <Card title="Lieferung"> und </Card>. Die Prüfung findet die Bilder über alt, die Preise der drei Karten und die Absätze in .card.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Text, Links, Bilder, Tabellen · Barrierefreiheit · Eine Liste aus Daten · Die erste Komponente
- Geht weiter in: Listen und Keys · Bedingungen: leer, Fehler, lädt · Zustand · Abgeleiteter Zustand
- Aufgaben zum Thema: Komponenten