Teste das Verhalten statt der Implementierung, nutze die Doppelaufrufe von StrictMode und miss die Renders mit dem Profiler.
Diese Lektion in Kodokon öffnenDie Philosophie der Testing Library passt in einen Satz: Teste, was der Nutzer wahrnimmt, niemals die Implementierungsdetails. Konkret fragst du das DOM über den Accessibility-Baum ab: zuerst getByRole (die Königin der Queries, die nebenbei deine ARIA-Rollen validiert), dann getByLabelText, getByText und getByTestId nur als letzten Ausweg. Für Interaktionen bevorzuge user-event gegenüber fireEvent: Wo fireEvent.change ein isoliertes Event auslöst, reproduziert user.type die vollständige Sequenz (focus, keydown, keypress, input, keyup), genau wie eine echte Tastatur. Die findBy*-Varianten kombinieren eine Query mit einem asynchronen Warten: unverzichtbar nach einem Ladeschritt.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('adds a task to the list', async () => {
const user = userEvent.setup();
render(<TodoApp />);
await user.type(
screen.getByRole('textbox'),
'Review the report'
);
await user.click(
screen.getByRole('button', { name: /add/i })
);
expect(
await screen.findByText('Review the report')
).toBeInTheDocument();
});StrictMode ist ein Entwicklungswerkzeug ohne jegliche Wirkung in der Produktion. In der Entwicklung ruft es deine Komponentenfunktionen, State-Initialisierer und an setState übergebene Funktionen zweimal auf, um unreine Renders aufzuspüren: Liefern zwei Ausführungen unterschiedliche Ergebnisse, hast du einen versteckten Seiteneffekt. Seit React 18 lässt es außerdem jeden Effekt durch den Zyklus Mount, Unmount, Remount laufen: Ein Effekt, dessen Cleanup nicht perfekt symmetrisch ist (nie gekündigtes Abonnement, nie abgebrochene Anfrage), verrät sich sofort. Es ist eine direkte Vorbereitung auf Features, bei denen React einen Screen unmountet und dann wieder mountet, während sein State erhalten bleibt.
useEffect(() => {
const controller = new AbortController();
fetch('/api/user', { signal: controller.signal })
.then((res) => res.json())
.then(setUser)
.catch(() => {});
return () => controller.abort();
}, []);Zum Messen stellt React die Komponente <Profiler> und ihr grafisches Gegenstück in den DevTools bereit. Ihr onRender-Callback erhält unter anderem phase (mount, update oder nested-update), actualDuration (die Zeit, die tatsächlich für das Rendern des Teilbaums in diesem Commit aufgewendet wurde) und baseDuration (eine Schätzung der Zeit, um den gesamten Teilbaum ohne jede Memoisierung zu rendern). Der Abstand zwischen beiden misst die Wirksamkeit deiner memo und useMemo: Eine actualDuration weit unter baseDuration bedeutet, dass die Memoisierung für dich arbeitet.
import { Profiler } from 'react';
function App() {
return (
<Profiler
id="Sidebar"
onRender={(id, phase, actualDuration) => {
console.log(id, phase, actualDuration);
}}
>
<Sidebar />
</Profiler>
);
}baseDuration im Profiler-Callback dar?