React Fast Refresh: Next-Gen Hot Reloading erklärt
James Reed
Infrastructure Engineer · Leapcell

Vorwort
Zuerst wollen wir den Unterschied zwischen Live Reloading und Hot Reloading einführen:
- Live Reloading: Wenn eine Datei geändert wird, kompiliert Webpack sie neu und erzwingt eine Aktualisierung des Browsers. Dies führt zu einer globalen Aktualisierung (die gesamte Anwendung), was
window.location.reload()
entspricht. - Hot Reloading: Wenn eine Datei geändert wird, kompiliert Webpack das entsprechende Modul neu, aber die Aktualisierung behält den Zustand der Anwendung bei, was eine teilweise Aktualisierung ermöglicht.
Einführung
Fast Refresh ist eine offizielle Hot Module Replacement (HMR)-Lösung, die von React für React Native (v0.6.1) eingeführt wurde. Da ihre Kernimplementierung plattformunabhängig ist, ist Fast Refresh auch auf das Web anwendbar.
Aktualisierungsstrategien
- Wenn Sie eine Moduldatei bearbeiten, die nur React-Komponenten exportiert, aktualisiert Fast Refresh nur den Code des Moduls und rendert Ihre Komponenten neu. Sie können alles innerhalb der Datei bearbeiten, einschließlich Stile, Rendering-Logik, Event-Handler oder Effekte.
- Wenn das Modul keine React-Komponenten exportiert, führt Fast Refresh das Modul und alle Module, die es importieren, erneut aus. Wenn beispielsweise sowohl
Button.js
als auchModal.js
Theme.js
importieren, werden durch die Bearbeitung vonTheme.js
sowohlButton.js
als auchModal.js
aktualisiert. - Schließlich, wenn die Datei, die Sie bearbeiten, nur von einem Modul außerhalb des React-Renderbaums importiert wird, greift Fast Refresh auf ein vollständiges Neuladen zurück. Dies kann vorkommen, wenn eine Datei sowohl eine React-Komponente rendert als auch einen Wert exportiert, der von Nicht-React-Modulen verwendet wird. Wenn beispielsweise ein React-Komponentenmodul auch eine Konstante exportiert und ein Nicht-React-Modul diese importiert, kann Fast Refresh die Datei nicht isoliert aktualisieren. Erwägen Sie in solchen Fällen, den exportierten Wert in eine separate Datei zu verschieben und ihn in beide Module zu importieren. Dies ermöglicht es Fast Refresh, korrekt zu funktionieren.
Fehlerbehandlung
- Wenn während Fast Refresh ein Syntaxfehler auftritt, können Sie den Fehler beheben und die Datei erneut speichern. Der rote Fehlerbildschirm verschwindet. Das fehlerhafte Modul ist von der Ausführung blockiert, sodass Sie die App nicht neu laden müssen.
- Wenn ein Laufzeitfehler während der Modulinitialisierung auftritt (z. B. versehentlich
Style.create
anstelle vonStyleSheet.create
geschrieben wird), ermöglicht das Beheben des Fehlers das Fortsetzen der Fast Refresh-Sitzung. Der Fehlerbildschirm verschwindet und das Modul wird aktualisiert. - Wenn ein Laufzeitfehler innerhalb einer Komponente auftritt, setzt das Beheben des Fehlers auch die Fast Refresh-Sitzung fort. In diesem Fall wird React die Komponente mit dem aktualisierten Code erneut bereitstellen.
- Wenn sich die Komponente, die abstürzt, innerhalb einer Fehlergrenze befindet, wird Fast Refresh alle Knoten innerhalb der Fehlergrenze erneut rendern, sobald Sie den Fehler behoben haben.
Einschränkungen
Beim Bearbeiten von Dateien behält Fast Refresh den Komponentenstatus bei, wenn dies sicher ist. In den folgenden Fällen wird der Status jedoch nach einer Dateibearbeitung zurückgesetzt:
- Der lokale Status von Klassenkomponenten wird nicht beibehalten (nur der Status von funktionalen Komponenten und Hooks wird beibehalten).
- Wenn das Modul, das Sie bearbeiten, etwas anderes als React-Komponenten exportiert.
- Manchmal exportiert ein Modul eine Higher-Order Component (HOC), wie z. B.
createNavigationContainer(MyScreen)
. Wenn die zurückgegebene Komponente eine Klassenkomponente ist, wird der Status zurückgesetzt.
Da funktionale Komponenten und Hooks immer häufiger werden, wird sich die Bearbeitungserfahrung mit Fast Refresh weiter verbessern.
Tipps
- Standardmäßig behält Fast Refresh den Status in funktionalen Komponenten und Hooks bei.
- Wenn Sie eine Animation debuggen, die nur beim Mounten ausgeführt wird, möchten Sie möglicherweise ein vollständiges Remounten bei jeder Bearbeitung erzwingen. Fügen Sie dazu
// @refresh reset
an beliebiger Stelle in der Datei hinzu. Diese Direktive zwingt Fast Refresh, die in dieser Datei definierten Komponenten jedes Mal neu zu mounten, wenn Sie sie bearbeiten.
Hooks-Verhalten in Fast Refresh
Fast Refresh versucht, den Komponentenstatus während der Bearbeitung beizubehalten. Insbesondere behalten useState
und useRef
ihre vorherigen Werte bei, solange:
- Sie ihre Parameter nicht ändern.
- Sie die Reihenfolge der Hook-Aufrufe nicht ändern.
Hooks mit Abhängigkeiten – wie useEffect
, useMemo
und useCallback
– werden während Fast Refresh immer erneut ausgeführt, wobei Abhängigkeitslisten ignoriert werden.
Wenn Sie beispielsweise ändern:
useMemo(() => x * 2, [x]);
in
useMemo(() => x * 10, [x]);
Auch wenn x
unverändert bleibt, wird die Factory-Funktion erneut ausgeführt. Ohne dieses Verhalten würden sich Änderungen nicht in der Benutzeroberfläche widerspiegeln.
Dieser Mechanismus führt manchmal zu unerwartetem Verhalten. Beispielsweise wird selbst wenn ein useEffect
-Abhängigkeitsarray leer ist, Fast Refresh den Effekt dennoch einmal erneut ausführen. Es ist jedoch im Allgemeinen eine gute Praxis, useEffect
-Hooks zu schreiben, die eine gelegentliche erneute Ausführung verarbeiten können, was es einfacher macht, später neue Abhängigkeiten einzuführen.
Implementierungsdetails
Um feinere Aktualisierungen als HMR (Modulebene) und React Hot Loader (begrenzte Komponentenebene) zu erreichen, müssen wir Komponentenebene und sogar Hooks-Ebene zuverlässige Aktualisierungen unterstützen. Dies erfordert eine tiefe Integration mit React, da externe Mechanismen allein (wie Laufzeit-Patches oder Kompilierzeit-Transformationen) nicht ausreichen:
Fast Refresh ist eine Neuimplementierung von „Hot Reloading“ mit voller Unterstützung von React.
Dies bedeutet, dass zuvor unvermeidbare Probleme (z. B. die Handhabung von Hooks) jetzt mit Reacts Unterstützung gelöst werden können.
Im Kern verlässt sich Fast Refresh immer noch auf HMR, mit den folgenden Schichten:
- HMR-Mechanismus (z. B. Webpack HMR)
- Kompilierzeit-Transformation (
react-refresh/babel
) - Laufzeit-Erweiterungen (
react-refresh/runtime
) - Reacts integrierte Unterstützung (
React DOM 16.9+
oderreact-reconciler 0.21.0+
)
Im Gegensatz zu React Hot Loader, das Proxy-Komponenten verwendet, entfernt Fast Refresh diese Schicht, da React jetzt nativ Hot Replacement für funktionale Komponenten und Hooks unterstützt.
Fast Refresh besteht aus zwei Teilen, die beide innerhalb des react-refresh
-Pakets verwaltet werden:
- Babel Plugin (
react-refresh/babel
) - Runtime (
react-refresh/runtime
)
Es stellt diese Funktionen über verschiedene Einstiegspunkte bereit.
Wir können die Implementierung von Fast Refresh in vier Hauptaspekte unterteilen:
- Was macht das Babel-Plugin zur Kompilierzeit?
- Wie funktioniert die Runtime zur Ausführungszeit?
- Welche spezifische Unterstützung bietet React dafür?
- Wie integriert sich dieser Mechanismus in HMR?
1. Was macht das Babel-Plugin zur Kompilierzeit?
Auf hoher Ebene erkennt das Babel-Plugin von Fast Refresh alle Komponenten und benutzerdefinierten Hooks im Code und fügt Funktionsaufrufe ein, um Komponenten zu registrieren und Hook-Signaturen zu sammeln.
Vor der Transformation
function useFancyState() { const [foo, setFoo] = React.useState(0); useFancyEffect(); return foo; } const useFancyEffect = () => { React.useEffect(() => {}); }; export default function App() { const bar = useFancyState(); return <h1>{bar}</h1>; }
Nach der Transformation
var _s = $RefreshSig$(), _s2 = $RefreshSig$(), _s3 = $RefreshSig$(); function useFancyState() { _s(); const [foo, setFoo] = React.useState(0); useFancyEffect(); return foo; } _s(useFancyState, 'useState{ct{}', false, function () { return [useFancyEffect]; }); const useFancyEffect = () => { _s2(); React.useEffect(() => {}); }; _s2(useFancyEffect, 'useEffect{}'); export default function App() { _s3(); const bar = useFancyState(); return <h1>{bar}</h1>; } _s3(App, 'useFancyState{bar}', false, function () { return [useFancyState]; }); _c = App; var _c; $RefreshReg$(_c, 'App');
Die Funktionen _s
und _s2
sammeln Hook-Signaturen, während $RefreshReg$
Komponenten für Fast Refresh registriert.
2. Wie funktioniert die Runtime zur Ausführungszeit?
Im transformierten Code bemerken Sie möglicherweise zwei undefinierte Funktionen, die vom Babel-Plugin injiziert werden:
$RefreshSig$
: Sammelt benutzerdefinierte Hook-Signaturen.$RefreshReg$
: Registriert Komponenten.
Diese Funktionen stammen aus react-refresh/runtime
. Ein typisches Setup könnte so aussehen:
var RefreshRuntime = require('react-refresh/runtime'); window.$RefreshReg$ = (type, id) => { // Note: `module.id` is Webpack-specific; other bundlers may use different identifiers. const fullId = module.id + ' ' + id; RefreshRuntime.register(type, fullId); }; window.$RefreshSig$ = RefreshRuntime.collectCustomHooksForSignature;
Hier ist, wie sie der React Refresh Runtime API zugeordnet sind:
createSignatureFunctionForTransform
: Verfolgt Hook-Signaturinformationen.register
: Registriert Komponenten, indem ihre Referenzen (type
) eindeutigen IDs (id
) zugeordnet werden.
Wie createSignatureFunctionForTransform
funktioniert
Die Funktion createSignatureFunctionForTransform
verfolgt die Hook-Verwendung in drei Phasen:
- Initial Phase: Assoziiert eine Funktionssignatur mit der entsprechenden Komponente.
- Hook Collection Phase: Sammelt Informationen über benutzerdefinierte Hooks, die in der Komponente verwendet werden.
- Resolved Phase: Nach dem dritten Aufruf stoppt es die Aufzeichnung weiterer Änderungen (um unnötigen Overhead zu vermeiden).
export function createSignatureFunctionForTransform() { let savedType; let hasCustomHooks; let didCollectHooks = false; return function <T>( type: T, key: string, forceReset?: boolean, getCustomHooks?: () => Array<Function> ): T | void { if (typeof key === 'string') { if (!savedType) { savedType = type; hasCustomHooks = typeof getCustomHooks === 'function'; } if (type != null && (typeof type === 'function' || typeof type === 'object')) { setSignature(type, key, forceReset, getCustomHooks); } return type; } else { if (!didCollectHooks && hasCustomHooks) { didCollectHooks = true; collectCustomHooksForSignature(savedType); } } }; }
Wie register
funktioniert
Die Funktion register
verfolgt Komponentenaktualisierungen:
export function register(type: any, id: string): void { let family = allFamiliesByID.get(id); if (family === undefined) { family = { current: type }; allFamiliesByID.set(id, family); } else { pendingUpdates.push([family, type]); } allFamiliesByType.set(type, family); }
Hier ist, was passiert:
- Wenn die Komponente noch nicht registriert ist, wird sie zu einer globalen Komponentenregistrierung (
allFamiliesByID
) hinzugefügt. - Wenn die Komponente bereits vorhanden ist, wird sie einer Warteschlange für ausstehende Aktualisierungen (
pendingUpdates
) hinzugefügt. - Die ausstehenden Aktualisierungen werden später verarbeitet, wenn Fast Refresh ausgeführt wird.
Wenn Aktualisierungen angewendet werden, verschiebt performReactRefresh
ausstehende Aktualisierungen in eine aktive Aktualisierungstabelle (updatedFamiliesByType
), sodass React die neuesten Versionen von Funktionen und Komponenten nachschlagen kann:
function resolveFamily(type) { return updatedFamiliesByType.get(type); }
3. Welche Unterstützung bietet React für Fast Refresh?
Die React-Runtime bietet mehrere Funktionen für die Fast Refresh-Integration:
import type { Family, RefreshUpdate, ScheduleRefresh, ScheduleRoot, FindHostInstancesForRefresh, SetRefreshHandler, } from 'react-reconciler/src/ReactFiberHotReloading';
Eine Schlüsselfunktion ist setRefreshHandler
, die Fast Refresh mit dem React-Abgleichsprozess verbindet:
export const setRefreshHandler = (handler: RefreshHandler | null): void => { if (__DEV__) { resolveFamily = handler; } };
Wie Fast Refresh React-Aktualisierungen auslöst
Wenn Fast Refresh eine Aktualisierung erkennt, führt er Folgendes aus:
- Übergibt die Aktualisierungstabelle (
updatedFamilies
) an React. - Löst React-Aktualisierungen mit
scheduleRefresh
undscheduleRoot
aus:
export function performReactRefresh(): RefreshUpdate | null { const update: RefreshUpdate = { updatedFamilies, // Components that will re-render while preserving state staleFamilies, // Components that must be remounted }; helpersByRendererID.forEach((helpers) => { helpers.setRefreshHandler(resolveFamily); }); failedRootsSnapshot.forEach((root) => { const helpers = helpersByRootSnapshot.get(root); const element = rootElements.get(root); helpers.scheduleRoot(root, element); }); mountedRootsSnapshot.forEach((root) => { const helpers = helpersByRootSnapshot.get(root); helpers.scheduleRefresh(root, update); }); }
Wie React die aktualisierten Komponenten verwendet
React verwendet resolveFamily
, um die neueste Version von Komponenten oder Hooks abzurufen:
export function resolveFunctionForHotReloading(type: any): any { const family = resolveFamily(type); if (family === undefined) { return type; } return family.current; }
Während des Renderns tauscht React die alte Komponentenreferenz gegen die neue aus:
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { switch (workInProgress.tag) { case IndeterminateComponent: case FunctionComponent: case SimpleMemoComponent: workInProgress.type = resolveFunctionForHotReloading(current.type); break; case ClassComponent: workInProgress.type = resolveClassForHotReloading(current.type); break; case ForwardRef: workInProgress.type = resolveForwardRefForHotReloading(current.type); break; default: break; } }
4. Wie Fast Refresh sich in HMR integriert
Alles bisherige ermöglicht Aktualisierungen auf Komponentenebene, aber damit Fast Refresh funktioniert, benötigt es immer noch Integration mit HMR (Hot Module Replacement).
HMR-Workflow
-
Injizieren Sie die Runtime in die Anwendung, bevor Sie React laden:
const runtime = require('react-refresh/runtime'); runtime.injectIntoGlobalHook(window); window.$RefreshReg$ = () => {}; window.$RefreshSig$ = () => (type) => type;
-
Umschließen Sie jedes Modul mit der Fast Refresh-Registrierungslogik:
window.$RefreshReg$ = (type, id) => { const fullId = module.id + ' ' + id; RefreshRuntime.register(type, fullId); }; window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; try { // !!! Actual module source code !!! } finally { window.$RefreshReg$ = prevRefreshReg; window.$RefreshSig$ = prevRefreshSig; }
-
Nach der Verarbeitung aller Module hooken Sie sich in die HMR-APIs ein:
const myExports = module.exports; if (isReactRefreshBoundary(myExports)) { module.hot.accept(); // Depends on the bundler const runtime = require('react-refresh/runtime'); let enqueueUpdate = debounce(runtime.performReactRefresh, 30); enqueueUpdate(); }
isReactRefreshBoundary
bestimmt, ob ein Modul Hot Reloading unterstützt oder ein vollständiges Live Reload benötigt.
Verwendung in Webumgebungen
Obwohl Fast Refresh ursprünglich für React Native entwickelt wurde, ist seine Kernimplementierung plattformunabhängig, wodurch es auch für Webanwendungen verwendbar ist.
It’s originally shipping for React Native, but most of the implementation is platform-independent.
Um Fast Refresh in Webanwendungen zu verwenden, ersetzen Sie Metro (React Natives Bundler) durch Webpack und befolgen Sie die oben beschriebenen Integrationsschritte.
Wir sind Leapcell, Ihre erste Wahl für das Hosten von Node.js-Projekten.
Leapcell ist die Next-Gen Serverless Plattform für Webhosting, Async Tasks und Redis:
Mehrsprachige Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Stellen Sie unbegrenzt Projekte kostenlos bereit
- zahlen Sie nur für die Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: $25 unterstützt 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für müheloses Setup.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für umsetzbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur einfachen Bewältigung hoher Parallelität.
- Kein Betriebsaufwand – konzentrieren Sie sich einfach auf das Bauen.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ