Zum Inhalt springen
aviral gupta

// B3.3 · ca. 30 Min. · Einstieg

Rendern und Committen

Nach dieser Lektion nennen Sie die drei Schritte jeder Bildschirmaktualisierung in React, zählen, wie oft eine Komponente rendert, und sagen voraus, welche DOM-Knoten ein Commit ändert.

Lektion 3 von 6 in B3 Interaktivität hinzufügen

Danach können Sie

  • Die drei Schritte einer Bildschirmaktualisierung und die zwei Auslöser eines Renderns nennen
  • Erklären, dass Rendern heißt, dass React Ihre Komponenten aufruft, und die Aufrufe unter Strict Mode zählen
  • Vorhersagen, dass ein Commit nur DOM-Knoten mit geänderter Ausgabe ändert, sodass ein Eingabefeld seinen Text behält
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B3.2: State. Lamp hält zwei State-Variablen. Sie klicken dreimal auf den Button. Was zeigt er?

    function Lamp() {
      const [on, setOn] = useState(false);
      const [presses, setPresses] = useState(0);
    
      function handleClick() {
        setOn(!on);
        setPresses(presses + 1);
      }
    
      return (
        <button onClick={handleClick}>
          {on ? 'On' : 'Off'}, {presses} presses
        </button>
      );
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Sie tippen milk in das Eingabefeld und klicken dann auf Tick. Clock rendert mit der neuen Zeit erneut. Was zeigen die Überschrift und das Eingabefeld?

    function Clock() {
      const [time, setTime] = useState('10:00');
    
      return (
        <>
          <h1>{time}</h1>
          <input />
          <button onClick={() => setTime('10:01')}>Tick</button>
        </>
      );
    }
  3. Üben · Aufgabe 3 von 7

    Strict Mode ist an, wie im Runner dieses Kurses. Parent wird eingehängt, dann klicken Sie einmal auf seinen Button. Wie oft wird Child insgesamt ausgegeben?

    function Child() {
      console.log('Child');
      return <p>I have no props</p>;
    }
    
    function Parent() {
      const [count, setCount] = useState(0);
    
      return (
        <>
          <button onClick={() => setCount(count + 1)}>{count}</button>
          <Child />
        </>
      );
    }
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Stück Code oder Ereignis seinen Schritt zu.

  5. Üben · Aufgabe 5 von 7

    Ergänzen Sie die Methode, die das erste Rendern von App in die Root auslöst.

    const root = createRoot(document.getElementById('root'));
    root.____(<App />);
    root.(<App />);
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Vor dem Klick hält ein Programm das <button>-Element in einer Variablen, before. Nach einem Klick vergleicht es den Button auf der Seite mit before (===) und liest den Text. Was gibt es aus?

    function SaveButton() {
      const [saved, setSaved] = useState(false);
    
      return (
        <button onClick={() => setSaved(true)}>
          {saved ? 'Saved' : 'Save'}
        </button>
      );
    }
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: eine Stoppuhr. Schreiben Sie in main.jsx eine Komponente Stopwatch mit einem State seconds, beginnend bei 0. Sie zeigt die Sekunden in einem <h1>, etwa 0 s, dann ein beschriftetes Eingabefeld Lap name, dann einen Button +1 s, der eine Sekunde addiert. Führen Sie es aus, tippen Sie einen Rundennamen ein und klicken Sie ein paarmal auf +1 s. Notieren Sie, welche der drei Schritte jeder Klick durchläuft und warum der Rundenname bleibt.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Uhr, die Ihre Notiz behält

Der Runner löst das erste Rendern von Clock aus, wie Vites main.jsx: createRoot(document.getElementById('root')), dann root.render in <StrictMode>. Ein Lauf zeigt nur das erste Rendern, also spielen zwei Timer unten in main.jsx den Nutzer: Nach dem ersten Commit tippen sie eine Notiz in das Eingabefeld und klicken auf Tick, was State aktualisiert und ein erneutes Rendern auslöst. Dann geben sie die Überschrift aus, ob das Eingabefeld noch derselbe DOM-Knoten ist, und seinen Text. Im Browser bleibt die Seite aktiv: Tippen und klicken Sie selbst.

main.jsx

import { useState } from 'react';

export default function Clock() {
  const [time, setTime] = useState('10:00');

  return (
    <section>
      <h1>{time}</h1>
      <label>
        Note <input />
      </label>
      <button onClick={() => setTime('10:01')}>Tick</button>
    </section>
  );
}

// Trigger 1, the initial render, is done by the runner, as by Vite's main.jsx:
// createRoot(document.getElementById('root')).render(<StrictMode><Clock /></StrictMode>);

// Act like a user once the page is on screen: type a note, then click Tick.
setTimeout(() => {
  const input = document.querySelector('input');
  input.value = 'Call Ada';
  // Trigger 2: the click handler updates state.
  document.querySelector('button').click();

  setTimeout(() => {
    console.log('h1:', document.querySelector('h1').textContent);
    console.log('same input:', document.querySelector('input') === input);
    console.log('note:', input.value);
  }, 50);
}, 50);

Ausführen mit

npm run dev

Ausgabe

h1: 10:01
same input: true
note: Call Ada
<!-- #root -->
<div id="root"><section><h1>10:01</h1><label>Note <input></label><button>Tick</button></section></div>
  • root.render(...) im Runner ist der erste Auslöser; createRoot allein erzeugt nur die Root und zeigt nichts an.
  • Der Klick ruft setTime auf, die zweite Art von Auslöser, also ruft React Clock erneut auf.
  • Nur der Text des <h1> unterscheidet sich, also ändert der Commit nur diesen Text: Das Eingabefeld ist derselbe Knoten und behält Call Ada.
  • Der getippte Text ist der value des Eingabefelds, kein Attribut, deshalb zeigt der Schnappschuss <input> ohne ihn.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Übungen

Übung 1 von 2

Die Notiz über Save hinweg behalten

Draft zeigt eine Überschrift, ein Notizfeld und einen Button. Führen Sie es aus, tippen Sie eine Notiz und klicken Sie auf Save: Der Text ist weg, weil jeder Zweig eine andere Hülle zurückgibt und React ein neues <input> baut. Schreiben Sie Draft mit einem einzigen return: eine <section>, deren className nur dann saved ist, wenn saved true ist, ein <h2> mit Saved oder Draft, das beschriftete Notizfeld und ein Button mit Edit oder Save, der saved umschaltet. Die Notiz muss ihren Text behalten und derselbe DOM-Knoten bleiben.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    React behält das <input> nur, wenn es bei beiden Rendervorgängen an derselben Stelle im JSX steht, mit denselben Eltern.

  2. Hinweis 2

    Verwenden Sie eine <section> und schalten Sie nur um, was sich unterscheidet: className={saved ? 'saved' : undefined} und {saved ? 'Saved' : 'Draft'}.

  3. Hinweis 3

    Ein Button kann beides: onClick={() => setSaved(!saved)} mit dem Text {saved ? 'Edit' : 'Save'}.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import { useState } from 'react';

export default function Draft() {
  const [saved, setSaved] = useState(false);

  return (
    <section className={saved ? 'saved' : undefined}>
      <h2>{saved ? 'Saved' : 'Draft'}</h2>
      <label>
        Note <input />
      </label>
      <button onClick={() => setSaved(!saved)}>{saved ? 'Edit' : 'Save'}</button>
    </section>
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.jsx

import { useState } from 'react';

export default function Draft() {
  const [saved, setSaved] = useState(false);

  // TODO: the two returns put the note field in different wrappers.
  // Type a note and click Save: the text is gone.
  if (saved) {
    return (
      <div className="saved">
        <h2>Saved</h2>
        <label>
          Note <input />
        </label>
        <button onClick={() => setSaved(false)}>Edit</button>
      </div>
    );
  }

  return (
    <section>
      <h2>Draft</h2>
      <label>
        Note <input />
      </label>
      <button onClick={() => setSaved(true)}>Save</button>
    </section>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Draft from './main.jsx';

const heading = () => screen.getByRole('heading', {level: 2}).textContent;
const note = () => screen.getByRole('textbox', {name: 'Note'});

test('Save zeigt Saved, Edit wieder Draft', async () => {
  const user = userEvent.setup();
  render(<Draft />);
  await user.click(screen.getByRole('button', {name: 'Save'}));
  assert.equal(heading(), 'Saved', 'nach Save lautet die Überschrift ' + JSON.stringify(heading()));
  await user.click(screen.getByRole('button', {name: 'Edit'}));
  assert.equal(heading(), 'Draft', 'nach Edit lautet die Überschrift ' + JSON.stringify(heading()));
});

test('Die Notiz behält nach Save ihren Text', async () => {
  const user = userEvent.setup();
  render(<Draft />);
  await user.type(note(), 'Call Ada');
  await user.click(screen.getByRole('button', {name: 'Save'}));
  assert.equal(note().value, 'Call Ada', 'nach Save enthält die Notiz ' + JSON.stringify(note().value));
});

test('Die Notiz ist nach Save und Edit derselbe DOM-Knoten', async () => {
  const user = userEvent.setup();
  render(<Draft />);
  const before = note();
  await user.click(screen.getByRole('button', {name: 'Save'}));
  assert.ok(note() === before, 'nach Save hat React ein neues <input> auf die Seite gesetzt');
  await user.click(screen.getByRole('button', {name: 'Edit'}));
  assert.ok(note() === before, 'nach Edit hat React ein neues <input> auf die Seite gesetzt');
});

styles.css

.saved {
  background: #e8f5e9;
}

Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.

Programm ausführen:

npm run dev

Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.

Übung 2 von 2

React die Uhr aktualisieren lassen

Clock zeigt eine Uhrzeit, einen Tick-Zähler, ein Notizfeld und einen Button Tick. Der Handler schreibt die neue Zeit von Hand in das <h1>, also löst nichts ein Rendern aus: Der Zähler bleibt bei 0, und ein zweiter Tick zeigt wieder dieselbe Zeit. Ändern Sie handleTick so, dass es stattdessen den State minutes aktualisiert. Dann rendert React Clock, und der Commit ändert Zeit und Zähler, während die Notiz ihren Text behält.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Nur eine State-Aktualisierung löst ein erneutes Rendern aus; das DOM selbst zu ändern, teilt React nichts mit.

  2. Hinweis 2

    Der Handler braucht nur eine Zeile: setMinutes(minutes + 1);.

  3. Hinweis 3

    Löschen Sie die Zeile mit document.querySelector: Das JSX zeigt bereits format(minutes), und React committet den neuen Text.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import { useState } from 'react';

function format(minutes) {
  const hours = Math.floor(minutes / 60);
  const rest = minutes % 60;
  return `${hours}:${String(rest).padStart(2, '0')}`;
}

export default function Clock() {
  const [minutes, setMinutes] = useState(600);

  function handleTick() {
    setMinutes(minutes + 1);
  }

  return (
    <section>
      <h1>{format(minutes)}</h1>
      <p>Ticks: {minutes - 600}</p>
      <label>
        Note <input />
      </label>
      <button onClick={handleTick}>Tick</button>
    </section>
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.jsx

import { useState } from 'react';

function format(minutes) {
  const hours = Math.floor(minutes / 60);
  const rest = minutes % 60;
  return `${hours}:${String(rest).padStart(2, '0')}`;
}

export default function Clock() {
  const [minutes, setMinutes] = useState(600);

  function handleTick() {
    // TODO: this writes the DOM by hand, so nothing triggers a render.
    document.querySelector('h1').textContent = format(minutes + 1);
  }

  return (
    <section>
      <h1>{format(minutes)}</h1>
      <p>Ticks: {minutes - 600}</p>
      <label>
        Note <input />
      </label>
      <button onClick={handleTick}>Tick</button>
    </section>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Clock from './main.jsx';

const time = () => screen.getByRole('heading', {level: 1}).textContent;
const ticks = () => screen.getByText(/^Ticks:/).textContent;
const tick = () => screen.getByRole('button', {name: 'Tick'});

test('Ein Tick zeigt 10:01 und Ticks: 1', async () => {
  const user = userEvent.setup();
  render(<Clock />);
  await user.click(tick());
  assert.equal(time(), '10:01', 'nach einem Tick zeigt die Uhr ' + JSON.stringify(time()));
  assert.equal(ticks(), 'Ticks: 1', 'nach einem Tick zeigt der Zähler ' + JSON.stringify(ticks()));
});

test('Zwei Ticks zeigen 10:02', async () => {
  const user = userEvent.setup();
  render(<Clock />);
  await user.click(tick());
  await user.click(tick());
  assert.equal(time(), '10:02', 'nach zwei Ticks zeigt die Uhr ' + JSON.stringify(time()));
});

test('Die Notiz behält Knoten und Text, während die Uhr tickt', async () => {
  const user = userEvent.setup();
  render(<Clock />);
  const note = screen.getByRole('textbox', {name: 'Note'});
  await user.type(note, 'Call Ada');
  await user.click(tick());
  assert.ok(screen.getByRole('textbox', {name: 'Note'}) === note, 'nach einem Tick hat React ein neues <input> auf die Seite gesetzt');
  assert.equal(note.value, 'Call Ada', 'nach einem Tick enthält die Notiz ' + JSON.stringify(note.value));
});

Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.

Programm ausführen:

npm run dev

Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.

Häufige Fehler

In ein Element rendern, das nicht auf der Seite ist

import { createRoot } from 'react-dom/client';

function Banner() {
  return <p>Sale today</p>;
}

export default function App() {
  return <h1>Shop</h1>;
}

// A second root for a banner outside the app
createRoot(document.getElementById('banner')).render(<Banner />);

Was React ausgibt

Target container is not a DOM element.

Warum, und die Lösung

Eine Seite darf mehrere React-Roots haben, aber jede braucht einen DOM-Knoten. Diese Seite hat nur <div id="root">, also gibt getElementById('banner') null zurück, und der Lauf stoppt mit Uncaught Error. Prüfen Sie die id gegen das HTML oder fügen Sie das Element zuerst hinzu. Der Fehler erscheint auch, wenn das Skript läuft, bevor das Element existiert. Für eine App genügt eine Root: Rendern Sie <Banner /> in App.

ReactDOM.render aus einem alten Tutorial verwenden

import * as ReactDOM from 'react-dom';

export default function App() {
  return <h1>Hello</h1>;
}

ReactDOM.render(<App />, document.getElementById('root'));

Was React ausgibt

TypeError: ReactDOM.render is not a function

Warum, und die Lösung

ReactDOM.render wurde in React 18 als veraltet markiert und in React 19 entfernt, also stoppt der Lauf mit Uncaught TypeError (Formulierung von Chrome und Node.js; Firefox und Safari formulieren es anders). Importieren Sie createRoot aus 'react-dom/client' und rufen Sie createRoot(document.getElementById('root')).render(<App />) auf.

Die Root in der Komponentendatei noch einmal erzeugen

import { createRoot } from 'react-dom/client';

export default function App() {
  return <h1>Hello</h1>;
}

createRoot(document.getElementById('root')).render(<App />);

Was React ausgibt

You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before.

Warum, und die Lösung

Wie Vites main.jsx erzeugt der Runner dieses Kurses die Root für den Default-Export bereits selbst, also bekommt der Container eine zweite Root, und React warnt. Erzeugen Sie eine Root pro Container: Exportieren Sie entweder die Komponente und lassen Sie den Runner (oder main.jsx in Vite) sie rendern, oder entfernen Sie den Default-Export. Um den Bildschirm später zu aktualisieren, setzen Sie State; erzeugen Sie keine neue Root.

React im Browser: React 19.3.0 (Entwicklungs-Build) und React Testing Library, MIT, von dieser Website ausgeliefert; JSX übersetzt TypeScript 6.0.3, Apache-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Schritt 1: Etwas löst ein Rendern aus

Jede Bildschirmaktualisierung in React hat drei Schritte: Auslösen (trigger), Rendern (render), Committen (commit). Es gibt nur zwei Auslöser. Der erste ist das erste Rendern: createRoot(document.getElementById('root')) erzeugt eine Root für einen DOM-Knoten, und root.render(<App />) bittet React, App darin anzuzeigen. Vites main.jsx und der Runner dieses Kurses erledigen das für Sie; solange niemand root.render aufruft, wird nichts angezeigt. Der zweite Auslöser ist eine State-Aktualisierung: Sobald eine Komponente auf dem Bildschirm ist, stellt der Aufruf einer Set-Funktion wie setTime('10:01') ein Rendern dieser Komponente in die Warteschlange.

Schritt 2: React rendert, das heißt, es ruft Ihre Komponenten auf

Rendern heißt, dass React Ihre Komponenten aufruft. Beim ersten Rendern ruft React die Root-Komponente auf. Bei einem späteren Rendern ruft es die Komponente auf, deren State-Aktualisierung das Rendern ausgelöst hat. Der Vorgang ist rekursiv: Gibt diese Komponente andere Komponenten zurück, ruft React auch diese auf, und deren Kinder, bis es genau weiß, was auf den Bildschirm gehört. Ein Kind ohne Props rendert also erneut, wenn seine Elternkomponente rendert. Rendern muss eine reine Berechnung sein, wie in Lektion B2.6: In der Entwicklung ruft Strict Mode jede Komponente zweimal auf, um unreinen Code zu finden; ein console.log im Rumpf gibt also zwei Zeilen pro Rendern aus.

Schritt 3: React committet, dann zeichnet der Browser

Nach dem Rendern ändert React das DOM. Beim ersten Rendern setzt es alle neuen Knoten mit appendChild() auf den Bildschirm. Bei einem erneuten Rendern wendet es nur die minimalen Änderungen an, die es beim Rendern ermittelt hat: React ändert DOM-Knoten nur, wenn sich zwischen zwei Rendervorgängen etwas unterscheidet. Bekommt das <h1> einer Uhr eine neue Zeit, ändert React diesen Text und lässt das <input> daneben in Ruhe: derselbe Knoten, und Ihr eingetippter Text bleibt. Ist das Ergebnis dasselbe wie beim letzten Mal, fasst React das DOM gar nicht an. Danach zeichnet der Browser den Bildschirm neu; react.dev nennt das Painting.

Quellen

Zuletzt geprüft am 5. Oktober 2026