Startseite / Artikel

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.

Verlag: REACTAPP.TOP26 Min. Lesezeit6055 Wörter

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.
  • React Native: Ein Ref bietet imperativen Zugriff auf einen ScrollView.
  • 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?

  • Wird dieser Effekt tatsächlich mit etwas Externem synchronisiert, und sind seine Abhängigkeiten korrekt?
  • Könnte dieser Callback eine veraltete Schließfunktion lesen?
  • Enthält diese Referenz imperative Daten oder UI-Zustände, die eigentlich in useState gehören sollten?
  • Löst diese Memoisierung ein wirklich vorhandenes Problem?
  • Versteckt dieser benutzerdefinierte Hook nützliche Komplexität oder fügt lediglich Indirektheit hinzu?
  • Soll dieser Zustand lokal oder global sein?
  • Wo liegt das eigentliche Leistungsengpass?
  • 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.
  • Hooks: die alltäglichen fünf sowie useContext und useReducer, die spezialisierten Hooks (useLayoutEffect, useId, useImperativeHandle, useSyncExternalStore, useTransition, useDeferredValue) und wie man eigene Hooks entwirft.
  • Effects: genaue Abhängigkeiten, veraltete Werte in Callbacks, Aufräumen und Stornieren, Handhabung von Abonnements, Verhalten im Strict Mode sowie Erkennen, wann kein Effect benötigt wird.
  • Leistung: wie React.memo Props vergleicht, der Aufwand der Memoisierung, Profilierung von Render-Vorgängen, lange Listen, aufwändige Berechnungen und zu umfassende Abonnements am Store.
  • React Native: das Anwendungslebenszyklus-Modell über AppState, die Handhabung der Tastatur, native Module und Geräte-APIs, auf der nativen Seite registrierte Listener, Optimierung von Listen, die Trennung zwischen JavaScript- und Native-Aufgaben sowie die Art und Weise, wie die Plattform darstellt.
  • Architektur: lokaler versus gemeinsamer Zustand, Client-Daten versus Server-Daten, wo die Grenzen von benutzerdefinierten Hooks liegen, wofür jede Komponente verantwortlich ist, wer über jeden Wert verfügt, sowie die Gestaltung wiederverwendbarer Komponenten-APIs.
  • 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.