Die Funktion connect() aus der react-redux Bibliothek
war jahrelang die Standard-Methode, um React-Komponenten mit dem Redux
Store zu verbinden. Obwohl moderne React Hooks wie
useSelector und useDispatch heute die
bevorzugte Lösung darstellen, ist ein Verständnis von
connect() und den zugehörigen Mapping-Funktionen essentiell
für die Arbeit mit bestehenden Codebases und das Verständnis der
Evolution von React-Redux.
Das connect() Pattern basiert auf dem Higher-Order
Component (HOC) Ansatz und bietet eine deklarative Methode, um
Store-Daten und Action-Creators als Props an Komponenten zu übergeben.
Dabei werden zwei zentrale Funktionen verwendet:
mapStateToProps für den Zugriff auf Store-Daten und
mapDispatchToProps für das Dispatching von Actions.
Die connect() Funktion ist ein Higher-Order Component,
das eine reguläre React-Komponente entgegennimmt und eine “verbundene”
Version zurückgibt, die automatisch Zugriff auf den Redux Store hat.
Dieser Ansatz folgt dem Container-Komponenten Pattern, bei dem die
Geschäftslogik von der Präsentationslogik getrennt wird.
const ConnectedComponent = connect(
mapStateToProps,
mapDispatchToProps
)(YourComponent);Die ursprüngliche Komponente YourComponent bleibt dabei
völlig unabhängig von Redux und kann als reine Präsentationskomponente
entwickelt und getestet werden. Sie erhält alle benötigten Daten und
Funktionen über Props, ohne zu wissen, dass diese aus einem Redux Store
stammen.
Die Funktion mapStateToProps definiert, welche Teile des
Store-States als Props an die Komponente übergeben werden sollen. Sie
wird bei jeder State-Änderung aufgerufen und ermöglicht es, nur die
relevanten Daten zu extrahieren und bei Bedarf zu transformieren.
const mapStateToProps = (state: RootState) => ({
user: state.auth.currentUser,
isLoading: state.ui.isLoading,
todoCount: state.todos.items.length,
hasUnsavedChanges: state.todos.items.some(todo => todo.isDirty),
});Diese Funktion ist ein reiner Selektor, der ausschließlich auf Basis des aktuellen States neue Props berechnet. Sie sollte keine Seiteneffekte haben und deterministisch sein. Komplexere Selektoren können hier implementiert werden, etwa die Berechnung abgeleiteter Daten oder die Filterung von Listen basierend auf anderen State-Teilen.
Ein häufiger Fehler besteht darin, in mapStateToProps
den gesamten State oder große Objekte zu übergeben. Dies kann zu
unnötigen Re-Renderings führen, da React eine oberflächliche
Gleichheitsprüfung durchführt. Stattdessen sollten nur die wirklich
benötigten Primitive oder kleine Objekte extrahiert werden.
Die Funktion kann auch einen zweiten Parameter ownProps
erhalten, der die Props enthält, die von der Eltern-Komponente übergeben
wurden:
const mapStateToProps = (state: RootState, ownProps: { userId: string }) => ({
user: state.users.byId[ownProps.userId],
isCurrentUser: state.auth.currentUserId === ownProps.userId,
});Die Funktion mapDispatchToProps definiert, welche
Action-Creators als Props zur Verfügung gestellt werden. Es gibt zwei
Syntaxformen: die Funktions-Form und die Objekt-Form.
Die Funktions-Form gibt vollständige Kontrolle über das Dispatching:
const mapDispatchToProps = (dispatch: Dispatch) => ({
onLogin: (credentials: LoginCredentials) => dispatch(login(credentials)),
onLogout: () => dispatch(logout()),
onBulkUpdate: (updates: Update[]) => {
updates.forEach(update => dispatch(updateItem(update)));
dispatch(saveChanges());
},
});Die Objekt-Form ist kompakter und wird automatisch mit
dispatch() umhüllt:
const mapDispatchToProps = {
onLogin: login,
onLogout: logout,
updateItem,
};Beide Formen sind äquivalent, wenn nur einfache Action-Creators dispatcht werden sollen. Die Funktions-Form bietet jedoch mehr Flexibilität für komplexe Dispatch-Logik oder die Kombination mehrerer Actions.
Die korrekte Typisierung von connect() war historisch
eine Herausforderung, wurde aber mit dem ConnectedProps
Utility deutlich vereinfacht:
const connector = connect(mapStateToProps, mapDispatchToProps);
type PropsFromRedux = ConnectedProps<typeof connector>;
interface OwnProps {
title: string;
}
type Props = PropsFromRedux & OwnProps;
const MyComponent: React.FC<Props> = ({ user, onLogin, title }) => {
// Komponenten-Logik
};
export default connector(MyComponent);Dieser Ansatz gewährleistet vollständige Typsicherheit, ohne die Props manuell definieren zu müssen. TypeScript kann automatisch ableiten, welche Props aus dem Store kommen und welche von der Eltern-Komponente übergeben werden.
Das connect() Pattern implementiert automatische
Optimierungen durch oberflächliche Gleichheitsprüfungen. Eine Komponente
wird nur dann re-rendered, wenn sich die Referenzen der von
mapStateToProps zurückgegebenen Props ändern. Dies
bedeutet, dass die Rückgabe-Objekte konsistent strukturiert sein
sollten.
Problematisch wird es, wenn in mapStateToProps neue
Objekte oder Arrays erstellt werden:
// Problematisch - erstellt bei jedem Aufruf neue Arrays
const mapStateToProps = (state: RootState) => ({
completedTodos: state.todos.filter(todo => todo.completed),
todoStats: { total: state.todos.length, completed: state.todos.filter(todo => todo.completed).length },
});Für solche Fälle sollten Memoization-Bibliotheken wie Reselect verwendet werden, die teure Berechnungen cachen und nur bei relevanten State-Änderungen neu berechnen.
Ein verbreiteter Fehler ist die Übergabe von Callback-Funktionen
durch mapStateToProps, anstatt
mapDispatchToProps zu verwenden. Dies führt zu neuen
Funktions-Referenzen bei jedem Re-Render und bricht die
Performance-Optimierungen.
Ebenso problematisch ist die direkte Manipulation von State-Objekten innerhalb der Mapping-Funktionen. Redux State sollte als unveränderlich behandelt werden, auch in Selektoren.
Ein weiterer häufiger Fehler besteht darin, zu viele Daten aus dem Store zu extrahieren. Jede Änderung an einem beliebigen Teil des extrahierten States führt zu einem Re-Render, auch wenn die Komponente diese spezifischen Daten gar nicht nutzt.
Mit der Einführung von React Hooks wurden useSelector
und useDispatch als moderne Alternative zu
connect() eingeführt. Diese bieten eine direktere und oft
intuitivere API:
// Statt connect()
const MyComponent = () => {
const user = useSelector((state: RootState) => state.auth.currentUser);
const dispatch = useDispatch();
const handleLogin = (credentials: LoginCredentials) => {
dispatch(login(credentials));
};
return (/* JSX */);
};Hooks bieten mehrere Vorteile: weniger Boilerplate-Code, bessere TypeScript-Integration, einfacheres Testing und die Möglichkeit, mehrere Selektoren in einer Komponente zu verwenden, ohne komplexe Mapping-Funktionen zu erstellen.
Die Migration bestehender connect()-Komponenten zu Hooks
kann schrittweise erfolgen. Dabei sollte zunächst die Funktionalität 1:1
übertragen werden, bevor Optimierungen vorgenommen werden.
Bei der Migration ist besonders auf die Performance-Charakteristika
zu achten. Während connect() automatisch optimiert, müssen
bei Hooks gegebenenfalls React.memo() oder eigene
Optimierungen implementiert werden.
Obwohl Hooks die moderne Empfehlung darstellen, bleibt
connect() in bestimmten Szenarien relevant.
Legacy-Codebases verwenden oft durchgängig das
connect()-Pattern, und eine vollständige Migration ist
möglicherweise nicht wirtschaftlich.
Zudem kann connect() in sehr komplexen Komponenten mit
vielen Redux-Abhängigkeiten übersichtlicher sein, da die gesamte
Store-Integration in den Mapping-Funktionen gekapselt ist, anstatt über
die Komponente verteilt zu werden.
Das Verständnis von connect() ist auch für das
Verständnis anderer HOC-basierter Bibliotheken und Patterns in der
React-Welt wertvoll. Viele Konzepte wie die Trennung von Container- und
Präsentationslogik bleiben auch in der Hook-Ära relevant.
Die React DevTools bieten spezielle Unterstützung für
connect()-Komponenten und zeigen die HOC-Hierarchie klar
an. Dies kann beim Debugging hilfreich sein, um zu verstehen, welche
Props von Redux kommen und welche von Eltern-Komponenten.
Die Redux DevTools zeigen ebenfalls, welche Komponenten bei
bestimmten Actions re-rendern, was bei der Performance-Analyse von
connect()-basierten Anwendungen hilfreich ist.