React für hochfrequente Wearable-Daten optimieren
Optimieren Sie React für Echtzeit-Wearable-Daten: Vermeiden Sie Re-Renders mit useMemo, Throttling und Zustand für performante Dashboards.
Auf dieser Seite
Wearable-Technologie, von Fitness-Trackern bis zu kontinuierlichen Glukosemonitoren, erzeugt einen Strom von Echtzeitdaten. Als Entwicklerinnen und Entwickler besteht unsere Herausforderung darin, Weboberflächen zu erstellen, die diese hochfrequenten Datenströme – oft Dutzende von Aktualisierungen pro Sekunde – visualisieren können, ohne den Browser der Nutzerinnen und Nutzer in ein träges, nicht reagierendes Durcheinander zu verwandeln. Eine React-Anwendung kann diesem Druck, wenn sie nicht sorgfältig aufgebaut ist, leicht nachgeben und zu einer frustrierenden Nutzererfahrung führen.
In diesem ausführlichen Beitrag behandeln wir das Problem der Leistung in React-Anwendungen, die hochfrequente Daten verarbeiten. Wir werden ein konzeptionelles „Health-Tracker“-Dashboard erstellen, das einen Strom simulierter Wearable-Daten empfängt. Wir beginnen damit, Leistungsengpässe zu identifizieren, und wenden dann systematisch eine Reihe von Optimierungstechniken an, um sicherzustellen, dass unsere Anwendung flüssig und reaktionsschnell bleibt.
Voraussetzungen: Sie sollten ein solides Verständnis von React-Hooks (useState, useEffect) haben. Vertrautheit mit einer Bibliothek zur Zustandsverwaltung ist hilfreich, aber nicht erforderlich. Sie benötigen eine funktionierende Node.js-Umgebung und einen Paketmanager wie npm oder yarn.
Warum das für Entwicklerinnen und Entwickler wichtig ist: Die Beherrschung dieser Optimierungstechniken ist nicht nur für Anwendungen mit Wearable-Daten entscheidend, sondern für jede Echtzeitanwendung, etwa Live-Finanz-Ticker, kollaborative Werkzeuge oder Dashboards für Online-Spiele.
Das Problem verstehen
Stellen Sie sich ein Wearable-Gerät vor, das alle 50 Millisekunden Aktualisierungen sendet. Das sind 20 Aktualisierungen pro Sekunde. Wenn jede Aktualisierung einen setState-Aufruf an der Spitze Ihres Komponentenbaums auslöst, besteht das Standardverhalten von React darin, diese Komponente und alle ihre Kinder neu zu rendern. Diese Kaskade von Neu-Renderings kann den Browser schnell überfordern und zu ausgelassenen Frames, Verzögerungen der Benutzeroberfläche und einer schlechten Nutzererfahrung führen.
Die zentralen Herausforderungen sind:
- Übermäßige Neu-Renderings: Komponenten, die nicht aktualisiert werden müssen, werden dennoch bei jedem Datenpunkt neu gerendert.
- Aufwendige Berechnungen: Daten ableiten oder komplexe Berechnungen bei jeder einzelnen Aktualisierung ausführen.
- Overhead der Zustandsverwaltung: Eine zentralisierte Lösung zur Zustandsverwaltung sendet Aktualisierungen möglicherweise zu breit und veranlasst dadurch nicht zusammenhängende Teile der Benutzeroberfläche zur Aktualisierung.
Unser Vorgehen besteht darin, das Problem zuerst zu diagnostizieren und dann gezielte Lösungen anzuwenden, beginnend mit den integrierten Werkzeugen von React und anschließend mit leistungsfähigeren, spezialisierten Bibliotheken.
Voraussetzungen
Richten wir eine einfache React-Anwendung ein, um unseren Wearable-Datenstrom zu simulieren.
- Node.js (v16 oder neuer)
- Create React App:
npx create-react-app wearable-dashboard - In das Projekt wechseln:
cd wearable-dashboard
Wir verwenden ein einfaches useEffect mit einem setInterval, um einen WebSocket oder eine ähnliche Echtzeit-Datenquelle zu simulieren.
Schritt 1: Das nicht optimierte Dashboard erstellen und Engpässe identifizieren
Erstellen wir zunächst ein einfaches Dashboard, das die Herzfrequenz, die Schritte und einen abgeleiteten Wert, „verbrannte Kalorien“, anzeigt.
Was wir tun
Wir erstellen eine einzelne Komponente, die den gesamten Zustand hält und die Daten anzeigt. Dies soll die Leistungsprobleme absichtlich demonstrieren.
Umsetzung
// src/components/HealthDashboard.js
import React, { useState, useEffect } from 'react';
// Ein simulierter Datenstrom
const mockWearableAPI = {
subscribe: (callback) => {
const intervalId = setInterval(() => {
const heartRate = Math.floor(Math.random() * (120 - 70 + 1)) + 70;
const steps = Math.floor(Math.random() * 10) + 1;
callback({ heartRate, steps });
}, 100); // Hochfrequente Aktualisierungen!
return () => clearInterval(intervalId);
},
};
const HealthDashboard = () => {
const [wearableData, setWearableData] = useState({ heartRate: 0, steps: 0 });
const [totalSteps, setTotalSteps] = useState(0);
useEffect(() => {
const unsubscribe = mockWearableAPI.subscribe((newData) => {
setWearableData(newData);
setTotalSteps((prevSteps) => prevSteps + newData.steps);
});
return () => unsubscribe();
}, []);
// Eine „aufwendige“ Berechnung
const calculateCaloriesBurned = (steps) => {
console.log('Calculating calories...');
// Eine komplexe Berechnung simulieren
let calories = steps * 0.04;
for (let i = 0; i < 1e5; i++) {
// Verarbeitung simulieren
}
return calories.toFixed(2);
};
const caloriesBurned = calculateCaloriesBurned(totalSteps);
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Health Dashboard</h1>
<p>❤️ Heart Rate: {wearableData.heartRate} bpm</p>
<p>👟 Total Steps: {totalSteps}</p>
<p>🔥 Calories Burned: {caloriesBurned}</p>
</div>
);
};
export default HealthDashboard;
So funktioniert es
Diese Komponente abonniert unsere simulierte API und ruft bei jedem neuen Datenelement setWearableData und setTotalSteps auf. In React 18 werden diese beiden Aktualisierungen automatisch zu einem einzigen Neu-Rendering zusammengefasst, was eine Verbesserung gegenüber älteren Versionen darstellt. Die Funktion calculateCaloriesBurned läuft jedoch bei jedem einzelnen Rendering, selbst wenn sich totalSteps für die Wahrnehmung der Nutzerinnen und Nutzer nicht auf bedeutsame Weise geändert hat.
Das Problem mit dem React Profiler identifizieren
Um das Problem zu sehen, können wir den Profiler der React Developer Tools verwenden.
- Öffnen Sie die Entwicklerwerkzeuge Ihres Browsers und wechseln Sie zum Tab „Profiler“.
- Klicken Sie auf die Schaltfläche „Record“.
- Lassen Sie die Anwendung einige Sekunden laufen.
- Beenden Sie die Aufzeichnung.
Sie sehen ein Flame-Diagramm, das häufige und möglicherweise lange Renderings der Komponente HealthDashboard zeigt. Beachten Sie den Protokolleintrag „Calculating calories...“ in Ihrer Konsole, der in rascher Folge erscheint. Dies bestätigt, dass unsere aufwendige Berechnung ein Engpass ist.
Schritt 2: Aufwendige Berechnungen mit useMemo memoizieren
Unsere erste Optimierung besteht darin, zu verhindern, dass die Funktion calculateCaloriesBurned unnötig ausgeführt wird.
Was wir tun
Wir umschließen die Berechnung von caloriesBurned mit dem Hook useMemo. Dadurch wird sichergestellt, dass die Berechnung nur erneut ausgeführt wird, wenn sich ihre Abhängigkeit (totalSteps) tatsächlich ändert.
Umsetzung
// src/components/HealthDashboard.js (aktualisiert)
import React, { useState, useEffect, useMemo } from 'react';
// ... mockWearableAPI ...
const HealthDashboard = () => {
// ... Zustand und useEffect ...
const calculateCaloriesBurned = (steps) => {
console.log('Calculating calories...');
let calories = steps * 0.04;
for (let i = 0; i < 1e5; i++) {
// Verarbeitung simulieren
}
return calories.toFixed(2);
};
// Das Ergebnis der aufwendigen Berechnung memoizieren
const caloriesBurned = useMemo(() => {
return calculateCaloriesBurned(totalSteps);
}, [totalSteps]);
// ... return-Anweisung ...
};
So funktioniert es
useMemo speichert den Rückgabewert einer Funktion im Cache bzw. „memoisiert“ ihn. Die Funktion wird nur erneut ausgeführt, wenn sich eine der Abhängigkeiten im Abhängigkeitsarray (in diesem Fall [totalSteps]) seit dem letzten Rendering geändert hat. Nun wird unser console.log für „Calculating calories...“ nur ausgelöst, wenn totalSteps aktualisiert wird.
Schritt 3: Zustandsaktualisierungen drosseln für eine flüssigere Benutzeroberfläche
Selbst mit useMemo wird unsere Komponente weiterhin zehnmal pro Sekunde neu gerendert. Für einen Menschen ist diese Detailtreue oft unnötig und kann weiterhin zu einer trägen Benutzeroberfläche führen, insbesondere wenn der Komponentenbaum groß ist. Ein besserer Ansatz besteht darin, Aktualisierungen zu bündeln und sie in einer Frequenz zu rendern, die für das menschliche Auge flüssig wirkt, etwa alle 500 ms.
Was wir tun
Wir implementieren Throttling, um zu begrenzen, wie oft wir den React-Zustand aktualisieren. Throttling stellt sicher, dass eine Funktion höchstens einmal in einem festgelegten Zeitraum aufgerufen wird.
Umsetzung
Wir können eine Bibliothek wie lodash für eine robuste throttle-Implementierung verwenden.
npm install lodash.throttle
// src/components/HealthDashboard.js (aktualisiert)
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import throttle from 'lodash.throttle';
// ... mockWearableAPI ...
const HealthDashboard = () => {
const [wearableData, setWearableData] = useState({ heartRate: 0, steps: 0 });
const [totalSteps, setTotalSteps] = useState(0);
// useRef, um die neuesten Daten zu halten, ohne Neu-Renderings auszulösen
const latestData = useRef({ heartRate: 0, steps: 0, newSteps: 0 });
useEffect(() => {
const throttledUpdate = throttle(() => {
setWearableData({ heartRate: latestData.current.heartRate });
setTotalSteps((prevSteps) => prevSteps + latestData.current.newSteps);
latestData.current.newSteps = 0; // Angesammelte Schritte zurücksetzen
}, 500); // Zustand höchstens einmal alle 500 ms aktualisieren
const unsubscribe = mockWearableAPI.subscribe((newData) => {
latestData.current.heartRate = newData.heartRate;
latestData.current.newSteps += newData.steps;
throttledUpdate();
});
return () => {
throttledUpdate.cancel(); // Die gedrosselte Funktion bereinigen
unsubscribe();
};
}, []);
// ... useMemo für caloriesBurned ...
return (
// ... JSX bleibt gleich
);
};
So funktioniert es
- Wir führen eine
useRefnamenslatestDataein. Refs eignen sich hervorragend zum Speichern veränderlicher Werte, die bei einer Änderung kein Neu-Rendering auslösen. - Innerhalb unseres
subscribe-Callbacks, der weiterhin alle 100 ms ausgelöst wird, aktualisieren wirlatestData.current. Dies ist eine sehr günstige Operation. - Wir rufen eine Funktion
throttledUpdateauf. Diese Funktion führt unseresetState-Aufrufe höchstens einmal alle 500 ms aus. - Wir sammeln die Schritte in
latestData.current.newStepsund setzen sie nach jeder Zustandsaktualisierung zurück, um keinen Datenverlust zu verursachen.
Wenn Sie nun den Profiler verwenden, sehen Sie, dass Renderings viel seltener sind, was zu einer leistungsfähigeren Anwendung führt.
Schritt 4: Feingranulare Zustandskontrolle mit Zustand oder Jotai
Unsere derzeitige Lösung ist gut, aber was geschieht, wenn unser Dashboard wächst? Was, wenn die Anzeige „verbrannte Kalorien“ in einem völlig anderen Teil des Komponentenbaums liegt? Mit unserem derzeitigen Aufbau wird das gesamte HealthDashboard neu gerendert.
Hier glänzen Bibliotheken zur Zustandsverwaltung wie Zustand und Jotai. Sie ermöglichen es Komponenten, nur die spezifischen Zustandsteile zu abonnieren, die sie betreffen, und verhindern so unnötige Neu-Renderings.
Alternativer Ansatz 1: Zustand
Zustand ist eine minimalistische Bibliothek zur Zustandsverwaltung, die Hooks verwendet. Sie wird oft als einfachere Alternative zu Redux angesehen.
Einrichtung: npm install zustand
// src/stores/wearableStore.js
import { create } from 'zustand';
export const useWearableStore = create((set) => ({
heartRate: 0,
totalSteps: 0,
updateData: (newData) =>
set((state) => ({
heartRate: newData.heartRate,
totalSteps: state.totalSteps + newData.steps,
})),
}));
// src/components/HeartRateDisplay.js
import React from 'react';
import { useWearableStore } from '../stores/wearableStore';
const HeartRateDisplay = () => {
const heartRate = useWearableStore((state) => state.heartRate);
console.log('Rendering HeartRateDisplay');
return <p>❤️ Heart Rate: {heartRate} bpm</p>;
};
export default React.memo(HeartRateDisplay); // Memoizieren, um Neu-Renderings durch die Elternkomponente zu verhindern
// src/components/StepCounter.js
import React from 'react';
import { useWearableStore } from '../stores/wearableStore';
const StepCounter = () => {
const totalSteps = useWearableStore((state) => state.totalSteps);
console.log('Rendering StepCounter');
return <p>👟 Total Steps: {totalSteps}</p>;
};
export default React.memo(StepCounter);
So funktioniert es: Bei Zustand verwendet jede Komponente eine Selektorfunktion ((state) => state.heartRate), um einen Teil des Stores zu abonnieren. Zustand rendert die Komponente nur neu, wenn sich der Rückgabewert dieses Selektors ändert. Selbst wenn sich totalSteps ändert, wird HeartRateDisplay also nicht neu gerendert.
Alternativer Ansatz 2: Jotai
Jotai verfolgt einen stärker „atomaren“ Ansatz. Der Zustand wird in winzige, unabhängige Teile zerlegt, die Atome genannt werden.
Einrichtung: npm install jotai
// src/stores/wearableAtoms.js
import { atom } from 'jotai';
export const heartRateAtom = atom(0);
export const totalStepsAtom = atom(0);
// src/components/HeartRateDisplay.js
import React from 'react';
import { useAtomValue } from 'jotai';
import { heartRateAtom } from '../stores/wearableAtoms';
const HeartRateDisplay = () => {
const heartRate = useAtomValue(heartRateAtom);
console.log('Rendering HeartRateDisplay');
return <p>❤️ Heart Rate: {heartRate} bpm</p>;
};
export default HeartRateDisplay;
// ... Ähnliche Komponente für StepCounter unter Verwendung von totalStepsAtom ...
So funktioniert es: In Jotai abonnieren Komponenten Atome direkt. Eine Aktualisierung eines Atoms veranlasst nur Komponenten, die dieses spezifische Atom (oder davon abgeleitete Atome) verwenden, zum Neu-Rendering. Dies bietet eine äußerst feingranulare Kontrolle über Neu-Renderings und macht es zu einer ausgezeichneten Wahl für Szenarien mit hochfrequenten Daten.
Alles zusammenfügen
Hier ist ein vollständiges Beispiel, das Zustand mit Throttling für eine robuste Lösung verwendet.
// src/App.js
import React, { useEffect, useRef } from 'react';
import { useWearableStore } from './stores/wearableStore';
import HeartRateDisplay from './components/HeartRateDisplay';
import StepCounter from './components/StepCounter';
import CalorieDisplay from './components/CalorieDisplay'; // Eine neue Komponente
import throttle from 'lodash.throttle';
const mockWearableAPI = {
subscribe: (callback) => {
const intervalId = setInterval(() => {
const heartRate = Math.floor(Math.random() * (120 - 70 + 1)) + 70;
const steps = Math.floor(Math.random() * 10) + 1;
callback({ heartRate, steps });
}, 100);
return () => clearInterval(intervalId);
},
};
function App() {
const updateData = useWearableStore((state) => state.updateData);
const throttledUpdate = useRef(
throttle((newData) => updateData(newData), 500)
).current;
useEffect(() => {
const unsubscribe = mockWearableAPI.subscribe(throttledUpdate);
return () => {
throttledUpdate.cancel();
unsubscribe();
};
}, [throttledUpdate]);
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Health Dashboard</h1>
<HeartRateDisplay />
<StepCounter />
<CalorieDisplay />
</div>
);
}
export default App;
In dieser abschließenden Struktur ist die Komponente App dafür verantwortlich, die Verbindung zur Datenquelle herzustellen und die Aktualisierungen des globalen Stores zu drosseln. Die einzelnen Anzeigekomponenten sind vollständig entkoppelt und werden nur neu gerendert, wenn das spezifische Zustandselement aktualisiert wird, das sie benötigen.
Fazit
Die Optimierung von React für hochfrequente Datenströme ist ein vielschichtiges Problem, das ein systematisches Vorgehen erfordert. Wir haben gesehen, wie man:
- Leistungsprobleme mit dem React Profiler diagnostiziert.
- Grundlegende Optimierungen wie
useMemoanwendet, um aufwendige Neuberechnungen zu verhindern. - Den Datenfluss steuert, indem man mit Throttling verhindert, dass React mit Zustandsaktualisierungen überfordert wird.
- Für Skalierbarkeit architektonisch plant, mit modernen Bibliotheken zur Zustandsverwaltung wie Zustand und Jotai für feingranulare, leistungsorientierte Aktualisierungen.
Durch die Kombination dieser Techniken können Sie hochgradig reaktionsschnelle und flüssige Oberflächen erstellen, die die anspruchsvollsten Echtzeit-Datenanwendungen bewältigen können.
Ressourcen
- Zustand-GitHub-Repository: github.com/pmndrs/zustand
- Jotai-Dokumentation: jotai.org
Kurzreferenz
| Aspekt | Wesentliche Information |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und Bewusstsein |
| Maßnahme | Besprechen Sie dies mit Ihrer Ärztin oder Ihrem Arzt |
| Häufigkeit | Wie von Ihrem Behandlungsteam empfohlen |
| Evidenz | Basierend auf aktuellen klinischen Leitlinien |
Häufig gestellte Fragen
F: Woran erkenne ich, ob dies auf mich zutrifft? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrer Ärztin oder Ihrem Arzt, um Ihre individuellen Risikofaktoren einzuschätzen und geeignete nächste Schritte festzulegen.
F: Was sollte ich meine Ärztin oder meinen Arzt fragen? A: Gute Fragen sind: „Was bedeuten meine Ergebnisse?“, „Welche Möglichkeiten habe ich?“, „Welche Änderungen des Lebensstils würden helfen?“ und „Wann sollte ich zur Nachuntersuchung kommen?“
F: Gibt es Änderungen des Lebensstils, die helfen können? A: Die meisten Erkrankungen profitieren von grundlegenden Maßnahmen: ausgewogene Ernährung, regelmäßige körperliche Aktivität (150+ Minuten/Woche), erholsamer Schlaf (7–9 Stunden), Stressbewältigung und der Verzicht auf Tabak.
Medizinischer Haftungsausschluss
Dieser Artikel dient ausschließlich Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie stets eine qualifizierte medizinische Fachkraft, bevor Sie Entscheidungen über Ihre Gesundheit, Behandlung oder Versorgung treffen. Die individuellen Umstände sind unterschiedlich, und was für eine Person angemessen ist, muss für eine andere nicht angemessen sein.
Wenn Sie einen medizinischen Notfall erleben, rufen Sie sofort 911 (oder Ihre örtliche Notrufnummer) an.