Abwägungsprozesse bei React Hooks anhand von Render-Snaps und Kompromissen.
Ein fortgeschrittenes Denkmodell für React und React Native Hooks: Render-Snaps, Effects, Refs, Memoisierung, benutzerdefinierte Hooks sowie Erklärungen von Kompromissen in Interviews.
Quellen: React & React Native Hooks: The Complete Senior Interview Guide
Bei Vorstellungsgesprächen für Senior-Entwickler mit React und React Native enden die Fragen selten bei „Was macht dieser Hook?“. Eine Frage zu useState führt innerhalb weniger Minuten zu Themen wie Batching und funktionalen Updates; eine Frage zu useEffect führt zu Diskussionen über endlose Schleifen, veraltete Schließfunktionen, Abbruchmechanismen sowie zu Situationen, in denen ein Effect überhaupt nicht vorhanden sein sollte; useMemo und useCallback führen direkt zur Frage „Macht das tatsächlich etwas schneller?“. Auswendig gelernte Definitionen halten solchen Nachfragen nicht stand. Was hingegen bestehen bleibt, ist ein zusammenhängendes Modell – und dieser Leitfaden entwickelt genau eines: Zunächst die Darstellung, anschließend den Zustand, Schließfunktionen, Effects, References, Memoisierung, benutzerdefinierte Hooks, Leistung sowie schließlich die Architektur, wobei überall dort React-Native-Beispiele verwendet werden, wo die Plattform die Situation verändert.
Das Darstellungsmodell, von dem alles andere abhängt
Begonnen Sie mit einer Idee und halten Sie daran fest: Wenn React eine Komponente rendernt, ruft er die Komponentenfunktion erneut von oben auf. Verwenden Sie den kleinstmöglichen Zähler.
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Es ist verständlich, anzunehmen, dass das Klicken auf die Schaltfläche in die Komponente vordringt und die Variable count ändert. Das ist jedoch nicht der Fall. Der Setter bittet React um eine neue Renderung, und beim nächsten Aufruf von Counter wird der neue Wert als frische Konstante übergeben.
Render 1
count = 0
↓
User clicks
↓
setCount(1)
↓
React schedules another render
↓
Render 2
count = 1
Jeder Render besitzt daher ein Abbild seiner Props und seines Zustands. Nichts innerhalb dieses Abbildes ändert sich danach noch. Diese Beobachtung erklärt Schließungen bezüglich alter Werte, veralteten Zustands in Timern, warum Abhängigkeiten von Effects existieren, wie Callbacks und Event-Handler funktionieren sowie warum die Memoisierung auf Identität beruht. Interviewer, die über die Oberfläche der API hinausgehen, prüfen in der Regel, ob Sie dieses Modell verstehen.
useState: Zustand, der zum Render gehört
useState gibt einer Funktionskomponente einen Zustandsteil zur Verfügung, der ihre Ausgabe bestimmt.
const [count, setCount] = useState(0);
Das Paar, das zurückgegeben wird, hat zwei unterschiedliche Rollen: einen Wert für den aktuellen Render und eine Funktion, die einen Update-Anfragen sendet.
count
↓
current state
setCount
↓
request a state update
Das Aufrufen des Setters ändert nichts vor Ort; es plant einen weiteren Render, bei dem die Komponente den neuen Wert sieht.
Wie sich die Fragen verschärfen
Eine typische Entwicklung sieht so aus:
- Grundlagen: Welches Problem löst
useState? - Mittelstufe: Worin unterscheidet sich der State von einer gewöhnlichen lokalen Variablen?
- Fortgeschritten: Wann ist die Updater-Funktion die richtige Wahl?
- Tiefergehend: Was erzeugt
setCount(count + 1), wiederholt dreimal in einem Handler, tatsächlich?
„Es speichert den State“ beantwortet nur die erste Frage. Eine stärkere Antwort umfasst das gesamte Konzept:
useState speichert einen Wert, der beeinflusst, was das Komponente darstellt. Der Aufruf des Setters stellt einen neuen Render mit dem aktualisierten Wert in die Warteschlange, weshalb der State am besten als Teil des Snapshots dieses Renders betrachtet werden sollte, nicht als Variable, die verändert werden muss. Wenn der nächste Wert vom vorherigen abhängt, ist die Updater-Funktion die sichere Wahl, da React Updates in Batches ausführt und der gespeicherte Wert bereits veraltet sein kann.
Im Gegensatz dazu wird eine gewöhnliche Variable bei jeder Darstellung mit ihrem ursprünglichen Wert neu erstellt, und das Ändern dieser Variable veranlasst React niemals zu einer erneuten Darstellung, weshalb sie die Benutzeroberfläche nicht steuern kann.
Warum funktionale Updates wichtig sind
Hier sind drei Inkremente, die auf dem gespeicherten Wert basieren.
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
Alle drei lesen denselben count aus dem aktuellen Snapshot, sodass nach der Gruppierung das Ergebnis ein Inkrement und nicht drei ist. Die Update-Formel behebt dieses Problem:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
React übermittelt jedem Updater den neuesten, noch nicht verarbeiteten Wert, sodass die drei Aufrufe korrekt nacheinander ausgeführt werden. Wenden Sie diese Formel an, wenn mehrere Updates gleichzeitig oder in der Warteschlange sind, wenn eine Callback-Funktion möglicherweise mit einem alten Snapshot ausgeführt wird, und im Allgemeinen immer dann, wenn der neue Wert aus dem alten berechnet wird.
Ein Einkaufskorb in einer React Native-App ist ein realistisches Beispiel. Die Menge wird angezeigt, daher gehört sie zum State:
const [quantity, setQuantity] = useState(1);
Die Handler ergeben den nächsten Wert aus dem vorherigen, und die Dekrementierung wird auf eins begrenzt, sodass der Korb niemals eine Menge von null oder negativ anzeigt.
const increaseQuantity = () => {
setQuantity(prev => prev + 1);
};const decreaseQuantity = () => {
setQuantity(prev => Math.max(1, prev - 1));
};
Es ändert sich nichts zwischen Web und Native
Der Hook ist auf beiden Plattformen identisch. Ein Dropdown im Web könnte überwachen, ob es geöffnet ist:
const [isOpen, setIsOpen] = useState(false);
Ein React Native-Bildschirm könnte die Sichtbarkeit eines Modals auf dieselbe Weise überwachen:
const [isModalVisible, setModalVisible] = useState(false);
Das State-Verfahren wird geteilt; nur die von ihm gesteuerten UI-Primitiven unterscheiden sich.
useEffect: Synchronisierung mit der Außenwelt
Kein Hook löst mehr Gesprächsstoff in Interviews aus. Die gängige Beschreibung „er führt Code nach der Renderung aus“ ist zwar richtig, lässt aber den Zweck unberücksichtigt. Eine bessere Formulierung lautet, dass ein Effect Ihre Komponente mit einem System synchron hält, das React nicht steuert.
Auf der Webseite kann dieses System DOM-Events, Browser-APIs, WebSockets, Abonnements, Timer oder Netzwerkanfragen sein. In React Native sind es oft AppState, das Keyboard-Modul, native Module, Zuhörer für Push-Benachrichtigungen, Netzwerkstatus, Geräte-APIs, WebSockets und Timer.
Wie sich die Fragen entwickeln
- Einfach: Wofür dient
useEffect? - Mittelstufe: Erklären Sie, wie der Abhängigkeitsarray funktioniert.
- Fortgeschritten: Was veranlasst einen Effect dazu, endlos zu laufen?
- Sehr fortgeschritten: In welchen Situationen ist ein Effect das falsche Werkzeug?
- Produktivumgebung: Wie deaktivieren Sie ein WebSocket-Abonnement?
Vergleichen Sie „useEffect dient für API-Aufrufe“ mit dem Folgenden:
useEffect existiert hauptsächlich dazu, eine Komponente mit einem externen System zu synchronisieren. Der Abhängigkeitsarray listet die reaktiven Werte auf, die der Effect liest, und muss genau sein; die Hooks-Lint-Regel erkennt Auslassungen. Wenn der Effect eine Abonnementsverbindung herstellt, einen Timer startet oder einen Zuhörer hinzufügt, gibt er eine Aufräumfunktion zurück, damit die alten Ressourcen freigegeben werden, bevor der Effect erneut ausgeführt wird oder die Komponente demontiert wird.
Auslesen des Abhängigkeitsarrays
Mit einer Abhängigkeit wird der Effect nach der ersten Renderung ausgeführt und erneut jedes Mal, wenn sich dieser Wert zwischen den Renderungen ändert.
useEffect(() => {
fetchUser(userId);
}, [userId]);
Ohne Array wird er nach jeder einzelnen Renderung ausgeführt:
useEffect(() => {
// ...
});
Mit einem leeren Array wird er nach dem Montieren der Komponente ausgeführt:
useEffect(() => {
// ...
}, []);
Achten Sie auf den Ausdruck „läuft einmal“. Im Entwicklungsmodus montiert Strict Mode absichtlich, räumt auf und montiert erneut, um Effekte aufzudecken, die sich nicht ordnungsgemäß aufräumen. Genauer gesagt läuft ein Effekt mit leeren Array nach dem Montieren, und der Entwicklungs-Strict Mode kann einen zusätzlichen Setup- und Aufräumzyklus hinzufügen. Code, der bei diesem doppelten Ausführen fehlschlägt, weist in der Regel einen echten Fehler auf.
Aufräumen verhindert Ressourcenlecks
Eine Abonnementfunktion, die von userId abhängt, öffnet eine Verbindung im Effektkörper:
useEffect(() => {
const subscription = subscribeToUpdates(userId);
und gibt eine Funktion zurück, die diese Verbindung schließt:
return () => {
subscription.unsubscribe();
};
}, [userId]);
Wenn sich userId ändert, führt React das vorherige Aufräumen aus, bevor der neue Effekt gestartet wird, sodass es niemals mehr als ein aktives Abonnement gibt:
Old effect
↓
cleanup old subscription
↓
new effect
↓
subscribe using new userId
Beim Entmontieren wird das letzte Aufräumen ausgeführt, und es bleibt nichts zurück:
component unmounts
↓
cleanup
↓
subscription removed
Derselbe Mechanismus wird auch zum Stopp von asynchroner Arbeit verwendet. Bei einer Anfrage kann man die Anfrage in der Aufräumfunktion mit einem AbortController abbrechen oder ein ignore-Flag setzen, damit eine späte Antwort von einem alten userId keine neueren Daten überschreibt.
Ein Beispiel aus React Native: Reaktion auf AppState
Ein überzeugenderer Effekt als ein Datenabruf ist einer, der die Komponente mit dem Lebenszyklus der nativen App verbindet. Dieser Listener aktualisiert die Daten, wenn die App wieder im Vordergrund erscheint, und entfernt sich beim Entfernen der Komponente.
useEffect(() => {
const subscription = AppState.addEventListener(
"change",
nextState => {
if (nextState === "active") {
refreshData();
}
}
);
return () => {
subscription.remove();
};
}, []);
Das ist die klarste Form der Synchronisierung mit einem externen System und zeigt dem Interviewer, dass man versteht, wofür Effekte dienen.
Fehler: Synchronisierung abgeleiteter Werte über einen Effekt
Nehmen wir an, ein Formular speichert Vor- und Nachname:
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
Ein häufiges Anti-Muster besteht darin, einen dritten Zustandswert sowie ein Effect hinzuzufügen, um diesen auf dem Laufenden zu halten:
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Dadurch entsteht überflüssiger Zustand und eine zusätzliche Renderung jedes Mal, wenn sich ein Name ändert – wobei bei einer Renderung der vollständige Name veraltet ist. Da der Wert vollständig aus dem vorhandenen Zustand abgeleitet wird, sollte er während des Renderings berechnet werden:
const fullName = `${firstName} ${lastName}`;
Das Prinzip, das man laut aussprechen sollte: Wenn ein Wert synchron aus Props oder Zustand berechnet werden kann, sollte er während des Renderings abgeleitet werden anstelle dessen gespeichert und in einem Effect korrigiert zu werden. Dieses Muster wird ausführlicher in „Stoppen des Synchronisierens von Zustand mit useEffect“ erläutert.
Fehler: instabile Objektabhängigkeiten
Hier wird innerhalb der Komponente ein Objektliteral erstellt und als Abhängigkeit aufgeführt.
const filters = {
status: "active",
};
useEffect(() => {
fetchUsers(filters);
}, [filters]);
Jeder Render erstellt ein neues filters-Objekt, und Abhängigkeiten werden per Referenz verglichen, wodurch der Effekt jedes Mal eine „Änderung“ wahrnimmt. Wenn der Effekt den Zustand aktualisiert, schließt sich der Kreislauf selbst:
Render
↓
new filters object
↓
dependency changed
↓
effect
↓
setState
↓
render
↓
new filters object
↓
effect again
Die reflexive Lösung besteht darin, das Objekt in eine Memoisierung zu packen:
useMemo(...)
Bevor man das tut, sollte man prüfen, ob das Objekt überhaupt innerhalb des Components erstellt werden muss. Eine Konstante kann außerhalb des Components platziert werden; ein aus Props gebildeter Wert kann oft durch Verwendung seiner primitiven Felder (zum Beispiel status) oder durch seine Erstellung innerhalb des Effekts ersetzt werden. Die Korrektur des Datenflusses ist der richtige Ansatz – Memoisierung hinzuzufügen, um dies zu verbergen, ist es nicht.
useRef: Speicher, der nicht gerendert wird
useRef gibt einen veränderbaren Container zurück, der über mehrere Renders hinweg bestehen bleibt und dessen Änderungen niemals einen Render auslösen.
const valueRef = useRef(0);
Man liest und schreibt seinen Inhalt über die Eigenschaft current:
valueRef.current
Durch Zuweisen eines neuen Wertes, wie unten gezeigt, wird der Container aktualisiert, doch das Display bleibt unverändert, bis etwas anderes eine Neuerstellung auslöst.
valueRef.current = 10;
Interviewer fragen in der Regel erst „Wie unterscheidet sich ein Ref von State?“, dann „Nennen Sie ein konkretes Beispiel“ und schließlich „Warum sollte man hier einen Ref statt State verwenden?“. Eine gute Antwort:
State sind Daten, die die Darstellung steuern; deren Änderung löst eine Neuerstellung aus. Ein Ref hält einen veränderbaren Wert über mehrere Neuerstellungen hinweg bei, ohne dass eine Neuerstellung stattfindet, wenn sich
.currentändert. Refs eignen sich für imperative Zugriffe, Timer-IDs, vorherige Werte sowie andere veränderbare Daten, die in der UI nicht angezeigt werden.
Auf der Webseite ermöglicht ein Ref einen imperativen Zugriff auf einen DOM-Node, zum Beispiel um ein Eingabefeld in den Fokus zu setzen:
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();
In React Native sind Refs genauso verbreitet, da mobile Benutzeroberflächen oft imperative Aufrufe benötigen, wie zum Beispiel das Zurückscrollen einer Liste an die Oberkante:
const scrollRef = useRef<ScrollView>(null);
scrollRef.current?.scrollTo({
y: 0,
animated: true,
});
Wahl zwischen State und Ref
Die entscheidende Frage ist einfach: Muss sich durch eine Änderung dieses Wertes auch das, was der Benutzer sieht, ändern? Wenn ja, wählen Sie
useState
und wenn nein, wählen Sie
useRef
Die aktive Registerkarte beeinflusst offensichtlich die Benutzeroberfläche, daher handelt es sich um State:
const [selectedTab, setSelectedTab] = useState("home");
Ein Timer-Handle existiert nur dafür, dass man ihn später löschen kann; nichts wird damit dargestellt, daher ist ein Ref der richtige Ort dafür:
const timerRef = useRef<number | null>(null);
useMemo: Speichern eines berechneten Wertes
useMemo speichert das Ergebnis einer Berechnung und rechnet sie nur dann erneut aus, wenn sich eine Abhängigkeit ändert. Eine gefilterte und sortierte Benutzerliste ist das klassische Beispiel:
const filteredUsers = useMemo(() => {
return users
.filter(user => user.active)
.sort(sortUsers);
}, [users]);
Solange users dieselbe Referenz behält, werden das Filtern und Sortieren bei nachfolgenden Darstellungen übersprungen. Achten Sie auf die versteckte Anforderung: sortUsers muss ebenfalls stabil sein oder als Abhängigkeit angegeben werden, da sort in-place mutiert – was hier nur sicher ist, weil filter bereits ein neues Array erzeugt hat.
Die folgende Frage lautet fast immer: „Sollten wir es überall verwenden?“ Die Antwort, die Urteilsvermögen zeigt:
Nicht unbedingt. useMemo ist nicht kostenlos: Es fügt Abhängigkeitsverfolgung, Speicherbedarf sowie zusätzlichen Code zum Lesen hinzu. Es lohnt sich, wenn eine aufwändige Berechnung unnötig wiederholt wird oder wenn eine stabile Referenz für etwas weiter im System wichtig ist – dabei sollte zunächst Profiling durchgeführt werden, bevor es hinzugefügt wird.
Das Übermaß an Verwendung zeigt sich typischerweise beim Memoisieren von Zeichenkettenverkettungen:
const fullName = useMemo(
() => `${firstName} ${lastName}`,
[firstName, lastName]
);
Die Aufgabe ist trivial, daher ist der einfache Ausdruck sowohl schneller zu lesen als auch mindestens genauso schnell auszuführen:
const fullName = `${firstName} ${lastName}`;
Betrachten Sie nun eine Trading-App in React Native, die Tausende von Transaktionen anzeigt, die nach Symbol und Zeitfenster gefiltert sowie neuesten zuerst sortiert sind.
const visibleTrades = useMemo(() => {
return trades
.filter(trade => trade.symbol === selectedSymbol)
.filter(trade => trade.timestamp >= startTime)
.sort((a, b) => b.timestamp - a.timestamp);
}, [trades, selectedSymbol, startTime]);
Wenn diese Transformation erheblich aufwendig ist, kommt die Memoisierung ins Spiel. Selbst dann sollten Sie dies mit einem Profiler überprüfen, anstatt es einfach anzunehmen.
useCallback: Beibehaltung der Funktionseinheit
useCallback gibt zwischen den Render-Vorgängen dasselbe Funktionselement zurück, solange sich seine Abhängigkeiten nicht ändern.
const handlePress = useCallback(() => {
submitOrder();
}, [submitOrder]);
Der Unterschied zu useMemo liegt nur darin, was gekachtet wird. Das eine kachtet einen Wert:
useMemo
→ memoises a value
Das andere kachtet eine Funktionseintragung:
useCallback
→ memoises a function reference
Tatsächlich verhält sich useCallback(fn, deps) wie useMemo(() => fn, deps). „Um die Leistung zu verbessern“ ist eine schwache Antwort auf die Frage „Warum sollte man es verwenden?“. Eine bessere Antwort lautet:
useCallback hält die Identität einer Funktion stabil, solange ihre Abhängigkeiten unverändert bleiben. Es lohnt sich, ihn dort zu verwenden, wo Referenzgleichheit tatsächlich eine Rolle spielt – beispielsweise bei einem Callback, der an ein gememorisches Kind übergeben wird, oder bei einer Funktion, die von einem anderen Hook als Abhängigkeit aufgeführt wird. Er sollte nicht standardmäßig hinzugefügt werden, denn die Memoisierung hat ihren eigenen Aufwand und erschwert das Verständnis des Codes.
Wie es mit React.memo verbunden ist
React.memo überspringt das Neuladen eines Components, wenn alle seine Props oberflächlich den vorherigen entsprechen.
const UserRow = React.memo(function UserRow({
user,
onPress,
}) {
return (
// ...
);
});
Falls der Elternteil bei jedem Render eine neue Pfeffelfunktion überlässt, unterscheidet sich mindestens eine Prop immer:
<UserRow
user={user}
onPress={() => openUser(user.id)}
/>
Weil sich die Referenz ändert, scheitert der flache Vergleich und die in Memorie gespeicherte Zeile wird dennoch gerendert, was den Vorteil von React.memo unbemerkt zunichtemachen kann. Ein stabiler Handler sieht so aus:
const handlePress = useCallback(() => {
openUser(user.id);
}, [user.id]);
und wird anstelle der inline-Funktion weitergegeben:
<UserRow
user={user}
onPress={handlePress}
/>
Bedenken Sie die Einschränkung: Tun Sie dies nur, wenn Sie Belege dafür haben, dass das Kind teuer neu gerendert wird – nicht aus Gewohnheit.
Closures und das Problem der veralteten Werte
Closures sind eine Eigenschaft von JavaScript, keine Hook, aber ohne sie sind Hooks schwer zu verstehen. Ein Closure ist eine Funktion, die weiterhin Zugriff auf die Variablen des scopes hat, in dem sie erstellt wurde.
function createLogger(name: string) {
return () => {
console.log(name);
};
}
Die innere Funktion kann name noch lange nach dem Rückgabepunkt von createLogger abrufen.
Wie ein veraltetes Closure in React auftaucht
Betrachten Sie ein Komponent mit Zähler:
function Counter() {
const [count, setCount] = useState(0);
und zwei Schaltflächen, eine zur Erhöhung der Zahl und eine zum Protokollieren der Zahl nach einer einsekündigen Wartezeit:
const handleClick = () => {
setTimeout(() => {
console.log(count);
}, 1000);
}; return (
<>
<button onClick={handleClick}>
Log Count
</button> <button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</>
);
}
Nehmen wir an, der Protokollierungs-Handler wurde während einer Darstellung erstellt
count = 0
Falls man auf „Zahl protokollieren“ klickt und innerhalb dieser Sekunde mehrmals erhöht, wird dennoch die Wartezeit ausgewiesen
0
obwohl bereits weitere Darstellungen stattgefunden haben
count = 1
count = 2
count = 3
Der Timeout-Callback bezieht sich auf count aus der Darstellung, in der er erstellt wurde, und dieser Wert ändert sich niemals.
Erklärung im Vorstellungsgespräch
Funktionskomponenten funktionieren in Render-Snapsshots: Jeder Render hat seine eigenen Werte und seine eigenen Schließfunktionen. Ein Callback, der in einem Render erstellt wird, speichert die Werte dieses Renders, sodass asynchrone Abläufe wie Timer den alten Zustand beobachten können. Die Lösung hängt vom Bedarf ab: eine funktionale Aktualisierung, korrekte Effekt-Abhängigkeiten oder ein Ref, wenn tatsächlich der neueste veränderliche Wert benötigt wird, ohne einen Render auszulösen.
Diese Antwort nennt die Ursache und bietet drei Lösungen samt den jeweiligen Voraussetzungen – genau das sucht ein erfahrener Interviewer.
Custom Hooks: Verhalten verpacken
Custom Hooks sind der Ort, an dem die vorherigen Ideen zusammenfließen. Ein Custom Hook ist eine Funktion, deren Name mit use beginnt und die andere Hooks aufruft, wodurch Sie zustandsbasiertes Verhalten extrahieren und wiederverwenden können. Ein Profilloader beginnt damit, seinen Zustand zu deklarieren:
function useUserProfile(userId: string) {
const [data, setData] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
Dann wird die Abfrage in einem Effect ausgeführt, der nach userId sortiert ist, und es werden die drei Werte zurückgegeben:
useEffect(() => {
// fetch user
}, [userId]); return {
data,
loading,
error,
};
}
Ein Bildschirm verwendet diese Werte, ohne zu wissen, wie das Laden funktioniert:
function ProfileScreen({ userId }) {
const {
data,
loading,
error,
} = useUserProfile(userId);
und rendernt aus dem Ergebnis das, was er benötigt:
// UI
}
Beachten Sie, dass jede Komponente, die einen benutzerdefinierten Hook aufruft, ihren eigenen unabhängigen Zustand erhält; der Hook teilt sich die Logik, nicht die Daten.
Was der Interviewer wirklich prüft
Die Frage „Können Sie einen benutzerdefinierten Hook schreiben?“ bezieht sich eigentlich darauf, ob man eine wiederverwendbare verhaltensbasierte Abstraktion erkennen kann. Gute Kandidaten verhalten sich wie folgt:
useAuth()
useUserProfile()
useNetworkStatus()
usePagination()
useDebounce()
useKeyboard()
useAppState()
usePermissions()
useInfiniteScroll()
In React Native ist ein Hook für den Netzwerkstatus ein gutes Beispiel. Er abonniert Änderungen der Verbindbarkeit, speichert das Flag im Zustand und gibt die Abmeldfunktion als Cleanup-Funktion zurück.
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const unsubscribe = subscribeToNetworkChanges(
state => {
setIsConnected(state.isConnected);
}
);
return unsubscribe;
}, []);
return {
isConnected,
};
}
Ein Zahlungsbildschirm entscheidet anschließend auf der Grundlage des Ergebnisses weiter:
function CheckoutScreen() {
const { isConnected } = useNetworkStatus();
if (!isConnected) {
return <OfflineMessage />;
}
return <Checkout />;
}
Der Bildschirm hat keine Ahnung, wie Netzwerkabonnements strukturiert sind, und genau diese Unwissenheit ist der ganze Wert der Abstraktion.
Der Rest des Hook-Toolkits
In Senior-Interviews geht es oft über die fünf gängigsten Hooks hinaus, daher sollten Sie das umfangreichere Set kennen.
useContext
Es liest einen Wert ab, der weiter oben im Baum bereitgestellt wird.
const theme = useContext(ThemeContext);
Es eignet sich für gemeinsame, querverteilte Anliegen wie Theme, Authentifizierung, Lokalisierung und Konfiguration. Es ist nicht automatisch eine State-Management-Bibliothek; jeder Verbraucher wird neu gerendert, wenn sich der bereitgestellte Wert ändert, was bei häufig aktualisierten Daten wichtig ist.
useReducer
Wenn mehrere Zustände gleichzeitig ändern, verteilen separate Setter die Übergangslogik:
setLoading(...)
setData(...)
setError(...)
Ein Reducer zentralisiert dies:
const [state, dispatch] = useReducer(reducer, initialState);
Komponenten beschreiben, was passiert ist, und der Reducer entscheidet über den nächsten Zustand:
dispatch({
type: "FETCH_SUCCESS",
payload: data,
});
Durch das Modellieren von Übergängen als benannte Aktionen werden komplexe Zustände leichter vorhersehbar und testbar.
useLayoutEffect
Es hat die gleiche Struktur wie useEffect, wird aber synchron nachdem React den Baum aktualisiert hat und vor dem Zeichnen durch den Browser ausgeführt – genau das ist notwendig, wenn man die Layout-Struktur messen und anpassen muss, bevor der Benutzer ein Flackern wahrnimmt. React Native weist ähnliche Fälle auf, in denen die Timing-Struktur des Layouts wichtig ist. Es handelt sich dabei nicht um einen schnelleren useEffect; da er das Zeichnen blockiert, schadet ein übermäßiger Einsatz der Funktionalität der Reaktionsfähigkeit.
useId
Es erzeugt stabile, eindeutige IDs, hauptsächlich für Zugänglichkeitszwecke wie das Verknüpfen einer Beschriftung mit einem Eingabefeld. Es ist im Web besonders nützlich; in React Native, das keinen Browser-DOM besitzt, sind die Anwendungsfälle begrenzter.
useImperativeHandle
Dadurch kann eine Komponente genau bestimmen, was die angehängte Referenz preisgibt, anstatt den zugrunde liegenden Knoten offenzulegen. Eine wiederverwendbare Komponente könnte beispielsweise das Fokussieren freigeben:
ref.current?.focus();
oder das Scrollen nach oben:
ref.current?.scrollToTop();
Diese kontrollierte imperative Oberfläche ist besonders nützlich für gemeinsam genutzte React Native-Komponenten.
useSyncExternalStore
Es ist die unterstützte Methode, um aus einem außerhalb von React liegenden Store zu lesen und somit auch bei gleichzeitiger Darstellung konsistente Snapshots zu erhalten. Sie ist fortgeschritten, taucht aber in Architekturdiskussionen auf und bildet die Grundlage für State-Bibliotheken.
useTransition
Diese Funktion markiert bestimmte Aktualisierungen als nicht dringend. Konzeptionell sorgt das Dringende dafür, dass die Schnittstelle reaktiv bleibt:
Urgent update
↓
Keep UI responsive
während nicht dringende Aufgaben mit geringerer Priorität geplant werden können:
Non-urgent update
↓
Can be scheduled with lower priority
Dies ist in großen Anwendungen wichtig, wo eine aufwändige Neugenerierung des Inhalts mit dem Tippen oder Berühren konkurriert.
useDeferredValue
Es verschiebt die Bereitstellung eines Wertes, damit dringende Interaktionen weiterhin reaktiv bleiben. Das klassische Szenario:
User typing
↓
Keep input responsive
↓
Defer expensive filtering
Die Eingabe wird sofort aktualisiert, während die aufwändige Filterung erst mit einer verzögerten Kopie der Abfrage Schritt hält.
Die Regeln für Hooks
Zwei Regeln sorgen dafür, dass Hooks funktionieren, da React jeden Hook anhand seiner Aufrufreihenfolge identifiziert.
Rufe Hooks auf der obersten Ebene auf
Verpacke niemals einen Hook in eine Bedingung, Schleife oder ein vorzeitiges Zurückgeben:
if (isLoggedIn) {
useEffect(() => {});
}
Setze stattdessen die Bedingung innerhalb des Hooks:
useEffect(() => {
if (!isLoggedIn) {
return;
}
und liste den Wert auf, von dem er abhängt:
// logic
}, [isLoggedIn]);
Rufe Hooks nur aus React-Funktionen auf
Hooks gehören zu Funktionskomponenten und benutzerdefinierten Hooks, nicht zu willkürlichen Hilfsfunktionen oder Klassenmethoden.
Über Leistung sprechen
„Dieser Bildschirm ist langsam – was würden Sie tun?“ ist eine häufig gestellte Frage. Die Antwort „add useMemo und useCallback“ zeigt das falsche Vorgehen an. Eine bessere Antwort lautet:
Der erste Schritt besteht darin herauszufinden, wohin die Zeit tatsächlich geht: wie oft Komponenten gerendert werden, welche Berechnungen aufwändig sind, wo die Grenzen der Komponenten liegen, wie Listen gerendert werden, wie Zustandsänderungen übertragen werden und welche Plattformfunktionen involviert sind. Anschließend sollte man den eigentlichen Engpass analysieren und beheben.
Die Leistung von React Native umfasst mehr als nur das Rendering
Die Kosten eines mobilen Bildschirms sind auf mehrere Ebenen verteilt:
React rendering
↓
JavaScript execution
↓
Native platform work
↓
Layout
↓
Images
↓
Animations
↓
Lists
↓
Network
Daher kann bei Verzögerungen auf einem React Native-Bildschirm die Ursache nicht unbedingt
useState
oder
useEffect
Überhaupt nicht. Es könnte auch um aufwändige JavaScript-Operationen, zu viele Renderungen, große Listen, Bildverarbeitung, Ladezeit von Animationen, native Operationen, Netzwerkverzögerungen oder Zustandsabonnements gehen, die zu viele Komponenten aktivieren. Zu wissen, wo man in all diesen Schichten suchen muss, ist es, was erfahrene Mobilexperten ausmacht.
Eine große Liste von Transaktionen
Stellen Sie sich eine Finanz-App vor, die Tausende von Transaktionen darstellt. Die naive Version rechnet bei jeder Renderung den gesamten Ablauf erneut aus:
const visibleTransactions = transactions
.filter(...)
.sort(...)
.map(...);
Das Memoisieren von Filtern und Sortierfunktionen ist ein sinnvoller Schritt:
const visibleTransactions = useMemo(() => {
return transactions
.filter(...)
.sort(...);
}, [transactions]);
Das ist selten die vollständige Lösung. Man benötigt außerdem die Virtualisierung von Listen, kostengünstiges Rendern von Elementen, stabile Schlüssel, in Memos gespeicherte Zeilenkomponenten, begrenzte Abonnements auf Zustände, ein sinnvolles Bildladen sowie responsives Benutzerfeedback. In React Native ist die Wahl von FlatList oder FlashList sowie eine gute Konfiguration in der Regel weitaus wichtiger als ein einzelner useMemo. Die richtige Frage lautet, wo sich das Engpassproblem befindet – nicht, welchen Hook hinzugefügt werden soll.
Zustandsverwaltung und Architektur
Context im Vergleich zu Store-Bibliotheken
Auf die Frage „Wann würde man Context anstelle von Redux oder Zustand verwenden?“ gibt es keine eindeutige Antwort.
Das hängt vom Zustand ab. Der Kontext eignet sich gut für relativ stabile, übergreifende Werte wie Theme oder Konfiguration. Für komplexen Anwendungsstatus, der sich häufig ändert und Selektoren, vorhersehbare Übergänge oder fein abgestimmte Abonnements erfordert, ist in der Regel eine spezielle Zustandsbibliothek besser geeignet.
Die entscheidenden Konzepte sind das Eigentumsverhältnis und die Aktualisierungsfrequenz.
Nicht alles gehört in einen globalen Speicher
Ein häufiger architektonischer Fehler ist es, jeden Wert in einen einzigen Speicher zu leiten:
Everything
↓
Global store
Fragen Sie stattdessen, wer für jeden Zustandsanteil verantwortlich ist. Der lokale Komponentenzustand umfasst kurzlebige UI-Details:
isModalOpen
inputValue
selectedTab
Serverzustand sind Daten, die von einer API abgerufen werden; diese werden oft besser von einer speziellen Server-Zustandsbibliothek verwaltet, die Caching und Neuabrufung steuert:
user profile
products
orders
transactions
Der globale Client-Zustand ist die kleine Menge an Werten, die viele Bildschirme tatsächlich teilen:
theme
authentication state
application preferences
Im Web gehört ein Teil des Zustands in die URL, damit er bei Neu laden erhalten bleibt und als Link geteilt werden kann:
filters
search
pagination
Die zu merkende Regel: Der Zustand sollte im engsten sinnvollen Umfang gespeichert werden.
Zusammenfassung auf einem Investitionsbildschirm
Stellen Sie sich einen React Native-Investitionsbildschirm vor, der Marktdaten, ein ausgewähltes Symbol, Chartdaten, Portfolio-Daten, AppState-Verwaltung, Scrollfunktionen sowie eine aufwendige Chart-Transformation benötigt. Eine vereinfachte Struktur:
InvestmentScreen
│
├── useMarketData()
│
├── usePortfolio()
│
├── useAppState()
│
├── useRef()
│
├── useMemo()
│
└── useCallback()
│
▼
UI
Folgt ein Entwurf der Komponente. Zwei Dinge sind zu beachten: Der Auszug endet mit einigen Prosezeilen sowie Trennern, die kein gültiger Code sind, daher sollte er als Pseudocode betrachtet werden; außerdem ist setSymbol aus useState bereits stabil, wodurch der useCallback-Wrapper hier überflüssig ist und man den Setter direkt übergeben kann.
function InvestmentScreen() {
const [symbol, setSymbol] = useState("AAPL");
const scrollRef = useRef<ScrollView>(null);
const { data: marketData } = useMarketData(symbol);
const { portfolio } = usePortfolio();
const chartData = useMemo(() => {
return transformMarketData(marketData);
}, [marketData]);
const handleSymbolChange = useCallback(
(nextSymbol: string) => {
setSymbol(nextSymbol);
},
[]
);
return (
<ScrollView ref={scrollRef}>
<MarketHeader
symbol={symbol}
onSymbolChange={handleSymbolChange}
/>
<PriceChart data={chartData} />
<PortfolioSummary
portfolio={portfolio}
/>
</ScrollView>
);
}
---
Now each concept has a clear responsibility.
---
Jeder Hook hat nun eine einzige, erklärbare Verantwortung:
useState
→ selected symboluseRef
→ imperative ScrollView accessuseMemo
→ expensive chart transformationuseCallback
→ stable handler where usefulCustom Hooks
→ market and portfolio behaviouruseEffect
→ internal synchronization within those Hooks
Die Effects bleiben innerhalb der benutzerdefinierten Hooks, wo sie mit dem Marktticker sowie dem Lebenszyklus der Anwendung synchronisiert werden, und die eigentliche Ansicht liest sich fast wie eine Beschreibung ihrer Daten.
Antworten wie ein Senior-Engineer
Einen Schritt über die Frage hinausgehen
Ein häufiger Fehler besteht darin, nur auf die gestellte Frage zu antworten. Auf die Frage „Was ist useMemo?“ ist „Es merkt sich einen Wert“ zwar korrekt, aber unpräzise. Eine ausführlichere Antwort:
useMemo speichert einen berechneten Wert unter Verwendung seiner Abhängigkeiten als Schlüssel. Er ist nicht standardmäßig enthalten, da er Overhead verursacht und Komplexität hinzufügt. Er macht Sinn, wenn eine aufwändige Berechnung unnötig wiederholt wird oder wenn ein stabiler Referenzwert wichtig ist – idealerweise nachdem eine Profilanalyse das Engpassproblem bestätigt hat.
In wenigen Sätzen haben Sie Ihr Wissen über APIs, Ihr Bewusstsein für Leistung sowie Ihr Urteilsvermögen gezeigt.
Eine fünfstufige Antwortstruktur
Ein zuverlässiges Muster für jede Frage zu Hooks:
1. What is it?
2. Why does it exist?
3. Real example
4. Trade-off / pitfall
5. React Native example when relevant
Angewandt auf useRef:
- Was: ein persistentes, veränderliches Wert, das keine Neuansicht auslöst.
- Warum: für Daten, die über Neuansichten hinweg bestehen bleiben müssen, aber nicht Teil der Benutzeroberfläche sind.
- Beispiel: eine Timer-ID oder ein Handle für imperative Komponenten.
- Kompromiss: Da dessen Aktualisierung keine Neuansicht auslöst, darf er keinen UI-Zustand enthalten.
Zu erwartende Nachfragen zur Vorbereitung
Zu useState sollten Sie bereit sein, über Update-Funktionen und das Bündeln mehrerer Aufrufe zu sprechen, warum das Ändern einer normalen Variablen den Bildschirm unverändert lässt, was eine Neurenderung auslöst, wo ein bestimmter Zustand gespeichert werden sollte und wie man auf wirklich abgeleiteten Zustand verzichten kann.
Zu useEffect können Fragen zur Rolle des Abhängigkeitsarrays, veralteten Schließungen, wiederholten Ausführungen, der Funktion der Cleanup-Funktion, dem Abbrechen laufender asynchrone Aufgaben, Situationen, in denen ein Effect das falsche Werkzeug ist, sowie zur doppelten Aufrufung im Strict Mode in der Entwicklung gestellt werden.
Zu useRef sollten Sie erklären können, warum das Schreiben in .current keine Neurenderung auslöst, in welchen Fällen ein Ref Vorteile gegenüber Zustand hat, wie Refs an DOM- oder nativen Elementen befestigt werden und was eine imperative API bedeutet.
Bei useMemo sollte man klar wissen, ob es die Leistung immer verbessert, wie es sich von useCallback unterscheidet, was referenzielle Gleichheit bedeutet, wann es richtig ist, es zu entfernen, und wie man nachweisen kann, dass es geholfen hat.
Bei useCallback muss man wissen, warum die Identität einer Funktion wichtig sein kann, wie sie mit React.memo kombiniert wird, wann sie nichts bringt, ob sie die Leistung verlangsamen kann, und was in ihre Abhängigkeitsliste gehören sollte.
Eine Debugging-Übung
Interviewer geben gerne einen fehlerhaften Component weiter. Hier ist einer mit mehreren Problemen.
function UserList({ users }) {
const [search, setSearch] = useState("");
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.includes(search)
);
}, [users]);
useEffect(() => {
console.log("Search:", search);
}, []);
return (
<List
data={filteredUsers}
renderItem={({ item }) => (
<UserRow
user={item}
onPress={() => selectUser(item.id)}
/>
)}
/>
);
}
Fehlende Abhängigkeit in useMemo
Die Memoisierung basiert nur auf den Benutzern:
}, [users]);
doch der Filter liest auch
search
daher wird die Liste während des Tippen durch den Benutzer nie aktualisiert. Die Abhängigkeiten müssen beide berücksichtigen:
}, [users, search]);
Die Lint-Regel würde dies sofort markieren.
Ein Effekt, der auf dem ersten Wert festgelegt ist
Dieser Effekt weist ein leeres Array auf:
useEffect(() => {
console.log(search);
}, []);
deshalb wird er nur nach dem Mount ausgeführt und protokolliert die anfängliche search-Aktion. Wenn das Ziel darin besteht, jede Änderung zu protokollieren, muss man sich darauf verlassen:
useEffect(() => {
console.log(search);
}, [search]);
Die inline Callback-Funktion, die mit Bedacht behandelt wird
Der Zeilen-Handler
onPress={() => selectUser(item.id)}
ist bei jeder Neuansicht eine neue Funktion. Das an sich ist kein Fehler. Zuerst muss festgestellt werden, ob
UserRow
teuer in der Ausführung ist und ob tatsächlich unnötig neu gerendert wird. Erst mit diesen Belegen sollte man
useCallback
oder die Komponenten umstrukturieren, beispielsweise indem man dem Zeilen-Element die ID sowie einen stabilen Handler überlässt. Die Demonstration solcher Zurückhaltung gehört zur erwarteten Antwort.
Gute, solide und erfahrene Antworten
Der Fortschritt ist leicht im Gedächtnis zu behalten. Ein guter Entwickler kennt useCallback. Ein fähiger Entwickler weiß, wann er es verwenden sollte. Ein erfahrener Entwickler weiß, wann er es nicht verwenden sollte, und kann die Abwägung erklären. Derselbe Ansatz gilt für jeden Hook. Das Ziel ist niemals
Maximum Hooks
sondern vielmehr
Correct abstraction
+
Correct data flow
+
Correct performance trade-offs
Ein Satz an Konzepten für Web und Native
Ihr benötigt kein separates Wissen über Hooks für React und React Native. Im Web beinhalten die Beispiele in der Regel
DOM
browser events
Web APIs
WebSockets
browser storage
während die Diskussionen zu mobilen Anwendungen
AppState
Keyboard
native modules
device APIs
push notifications
network state
native event subscriptions
Doch das grundlegende Werkzeugset ist in beiden Fällen dasselbe React:
useState
useEffect
useRef
useMemo
useCallback
useContext
useReducer
Custom Hooks
closures
dependency arrays
Erklärt also zunächst das React-Konzept und zeigt anschließend, wie man es auf der jeweiligen Plattform anwendet. So bleibt eure Glaubwürdigkeit bei Web-, Mobile- und Hybridaufgaben gewahrt.
Kurzreferenz-Hilfssheet
useState
Gibt an, was bei Aktualisierung eine Neurenderung auslöst. Typische Anwendungen:
form values
selected tabs
modal visibility
loading state
local UI state
useEffect
Synchronisierung mit externen Systemen. Typische Anwendungen:
subscriptions
timers
WebSockets
AppState
native events
network operations
Vermeiden Sie es bei Werten, die während der Renderung abgeleitet werden können.
useRef
Ein dauerhafter, veränderlicher Wert, der nicht gerendert wird. Typische Anwendungen:
DOM/native refs
timers
previous values
imperative APIs
mutable non-UI data
useMemo
Ein gememorisierter, berechneter Wert. Greifen Sie darauf zu, wenn
calculation is expensive
or
referential identity matters
und messen Sie zunächst.
useCallback
Eine gememorisierte Funktionseintragung. Es lohnt sich, wenn
function identity matters
React.memo is involved
another Hook depends on function identity
Fügen Sie sie nicht automatisch hinzu.
useContext
Liest gemeinsam genutzte Kontextdaten, wie zum Beispiel:
theme
locale
authentication
configuration
useReducer
Modelliert komplexe Übergänge. Nützlich, wenn Sie
many related state variables
complex transitions
state machine-like behaviour
Custom Hooks
wiederverwendbares, zustandsbasiertes Verhalten, zum Beispiel:
useAuth()
useNetworkStatus()
useAppState()
usePagination()
useKeyboard()
useUserProfile()
Ein mentales Modell statt eines Glossars
Anstatt sich für jeden Hook eine einwortige Erklärung einzuprägen,
useState = state
useEffect = side effect
useRef = reference
useMemo = memoisation
useCallback = callback
stellen Sie sich vor, wie sie mit der Darstellung zusammenhängen: Der UI-Zustand fließt über useState zu erneuten Darstellungen, abgeleitete Werte stammen von useMemo, veränderliche, nicht-UI-Daten befinden sich in useRef, und useEffect verbindet die dargestellte UI mit APIs, Ereignissen und nativen Systemen, wobei benutzerdefinierte Hooks alles zusammenfassen.
React Render
│
┌──────────┼──────────┐
│ │ │
UI State Derived Mutable
│ Values Data
│ │ │
useState useMemo useRef
│
▼
Re-render
│
▼
UI
│
▼
External Systems
│
▼
useEffect
│
▼
APIs / Events / Native
│
▼
Custom Hooks
Hinter all dem stehen einige grundlegende Prinzipien:
Rendering
+
Closures
+
Referential Equality
+
Data Flow
+
Performance
Sobald diese verstanden sind, werden die einzelnen Hooks einfacher.
Fragen für jede Code-Review
Hooks sind Werkzeuge zur Verwaltung verschiedener Verhaltensweisen in einer reaktiven Anwendung, und die entscheidende Fähigkeit besteht darin zu wissen, warum man jeden Hook verwendet. Diese Fragen eignen sich sowohl für Reviews als auch für Interviews:
- Muss dieser Zustand existieren, oder kann er abgeleitet werden?
useState gehören sollten?Zusammenfassung
Hooks werden einfacher, sobald man aufhört, sie als separate APIs zu betrachten, und sie stattdessen als Schritte in einem einzigen Ablauf sieht:
Rendering
↓
State
↓
Closures
↓
Effects
↓
References
↓
Memoisation
↓
Custom Hooks
↓
Performance
↓
Architecture
Wenn man diesen Ablauf im Hinterkopf hat, ist der Wechsel zwischen React Web und React Native weitaus weniger beängstigend. Wenn jemand fragt, warum man an einer bestimmten Stelle useMemo verwendet hat, sollte man die Lehrbuchdefinition überspringen und stattdessen die Geschichte erzählen: das Problem, das man festgestellt hat, was es verursacht hat, warum genau dieser Hook geeignet war, welche Abwägungen man getroffen hat und wie man das Ergebnis gemessen hat.
Vor einem Vorstellungsgespräch sollten Sie überprüfen, ob Sie natürlich über Folgendes sprechen können:
- Grundlagen: die Ursachen für eine Renderung, was ein Render-Snapshot ist, wie sich Zustand von einer gewöhnlichen Variable unterscheidet, Closures, referenzielle Gleichheit und Reconciliation.
useContext und useReducer, die spezialisierten Hooks (useLayoutEffect, useId, useImperativeHandle, useSyncExternalStore, useTransition, useDeferredValue) und wie man eigene Hooks entwirft.React.memo Props vergleicht, der Aufwand der Memoisierung, Profilierung von Render-Vorgängen, lange Listen, aufwändige Berechnungen und zu umfassende Abonnements am Store.Ziel ist es nicht, sich alles auswendig zu merken, sondern genügend Verständnis zu haben, um vor dem Interviewer ein unvertrautes Problem logisch durchdenken zu können.