Back

Redux in React verstehen: Verwalten Sie den State wie ein Profi

Redux in React verstehen: Verwalten Sie den State wie ein Profi

Haben Sie Schwierigkeiten, den State über mehrere Komponenten in Ihrer React-Anwendung zu verwalten? Mit wachsender App-Größe wird das Weitergeben von Props durch mehrere Komponentenebenen (Prop Drilling) unhandlich und fehleranfällig. Redux bietet eine Lösung, indem es ein zentralisiertes State-Management-System bereitstellt, das Ihre React-Anwendungen vorhersehbarer und leichter zu debuggen macht.

In diesem umfassenden Leitfaden erfahren Sie, wie Redux mit React funktioniert, wann Sie es verwenden sollten und wie Sie es effektiv in Ihren Projekten implementieren können. Wir behandeln alles von den Grundkonzepten bis zur praktischen Implementierung mit Redux Toolkit, der modernen Art, Redux-Code zu schreiben.

Wichtige Erkenntnisse

  • Redux bietet zentralisiertes State-Management für React-Anwendungen
  • Verwenden Sie Redux, wenn Ihre App komplexen gemeinsamen State über viele Komponenten hinweg hat
  • Redux folgt einem unidirektionalen Datenfluss: Actions → Reducers → Store → UI
  • Redux Toolkit vereinfacht die Redux-Entwicklung durch Reduzierung von Boilerplate-Code
  • Leistungsoptimierung ist entscheidend für größere Redux-Anwendungen

Was ist Redux in React?

Redux ist ein vorhersehbarer State-Container für JavaScript-Anwendungen, besonders beliebt bei React. Es speichert den gesamten State Ihrer Anwendung in einem einzigen, unveränderlichen Objekt, dem sogenannten ""Store"". Dieser zentralisierte Ansatz für das State-Management löst viele häufige Probleme in React-Anwendungen:

  • Prop Drilling: Kein Weitergeben von State durch mehrere Komponentenebenen mehr
  • State-Synchronisation: Gewährleistet konsistenten State über Komponenten hinweg
  • Vorhersehbare Updates: State-Änderungen folgen einem strengen unidirektionalen Datenfluss
  • Debugging: Macht State-Änderungen nachvollziehbar und vorhersehbar

Redux ist kein Teil von React selbst, funktioniert aber als ergänzende Bibliothek durch React Redux, die offizielle Redux UI-Binding-Bibliothek für React.

// Basic Redux integration with React
import { Provider } from 'react-redux';
import store from './store';

function App() {
  return (
    <Provider store={store}>
      <YourApplication />
    </Provider>
  );
}

Wann sollten Sie Redux verwenden?

Nicht jede React-Anwendung benötigt Redux. Erwägen Sie die Verwendung von Redux, wenn:

  • Ihre App komplexe State-Logik hat, die über viele Komponenten geteilt wird
  • Komponenten auf den State zugreifen und ihn von verschiedenen Teilen des Komponentenbaums aus aktualisieren müssen
  • Sie State-Änderungen in Ihrer gesamten Anwendung verfolgen und debuggen müssen
  • Ihre App eine mittlere bis große Codebasis mit mehreren Entwicklern hat

Für kleinere Anwendungen oder Komponenten mit lokalisiertem State ist das eingebaute State-Management von React (useState, useReducer und Context API) oft ausreichend.

Kernkonzepte von Redux

Der Store: Einzige Quelle der Wahrheit

Der Redux-Store enthält den gesamten State-Baum Ihrer Anwendung. Im Gegensatz zum Komponenten-State von React, der über Komponenten verteilt ist, zentralisiert Redux den gesamten State an einem Ort:

// Creating a Redux store
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';

const store = configureStore({
  reducer: rootReducer
});

export default store;

Actions: Beschreibung von State-Änderungen

Actions sind einfache JavaScript-Objekte, die beschreiben, was in Ihrer Anwendung passiert ist. Sie sind die einzige Möglichkeit, Daten an den Redux-Store zu senden:

// An action object
{
  type: 'counter/incremented',
  payload: 1
}

// Action creator function
const increment = (amount) => {
  return {
    type: 'counter/incremented',
    payload: amount
  }
}

Jede Action muss eine type-Eigenschaft haben, die beschreibt, um welche Art von Action es sich handelt. Die payload-Eigenschaft enthält alle für die Action benötigten Daten.

Reducers: Pure Funktionen für State-Updates

Reducers sind reine Funktionen, die den aktuellen State und eine Action nehmen und dann einen neuen State zurückgeben. Sie spezifizieren, wie sich der State der Anwendung als Reaktion auf Actions ändert:

// A simple reducer function
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'counter/incremented':
      return state + action.payload;
    case 'counter/decremented':
      return state - action.payload;
    default:
      return state;
  }
}

Reducers müssen:

  • Reine Funktionen sein (keine Nebeneffekte)
  • Den State niemals direkt verändern
  • Einen neuen State-Objekt zurückgeben, wenn Änderungen auftreten

Unidirektionaler Datenfluss

Redux folgt einem strengen unidirektionalen Datenfluss:

  1. Sie dispatchen eine Action aus einer Komponente
  2. Der Redux-Store übergibt die Action an den Reducer
  3. Der Reducer erstellt einen neuen State basierend auf der Action
  4. Der Store aktualisiert seinen State und benachrichtigt alle verbundenen Komponenten
  5. Komponenten rendern mit dem neuen State neu

Dieser Einweg-Fluss macht State-Änderungen vorhersehbar und leichter zu verstehen.

React Redux Integration

Provider: Redux mit React verbinden

Die Provider-Komponente macht den Redux-Store für alle Komponenten in Ihrer Anwendung verfügbar:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Hooks: Auf Redux in Komponenten zugreifen

React Redux bietet Hooks, die es Ihren Komponenten ermöglichen, mit dem Redux-Store zu interagieren:

useSelector: State lesen

import { useSelector } from 'react-redux';

function CounterDisplay() {
  // Select the counter value from the store
  const count = useSelector(state => state.counter.value);
  
  return <div>Current count: {count}</div>;
}

Der useSelector-Hook:

  • Nimmt eine Selector-Funktion, die Daten aus dem Store-State extrahiert
  • Rendert die Komponente neu, wenn sich der ausgewählte State ändert
  • Optimiert die Leistung, indem unnötige Neurenderings vermieden werden

useDispatch: State aktualisieren

import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

Der useDispatch-Hook:

  • Gibt die dispatch-Funktion des Stores zurück
  • Ermöglicht es Ihnen, Actions aus jeder Komponente zu dispatchen

Modernes Redux mit Redux Toolkit

Redux Toolkit ist die offizielle, empfohlene Art, Redux-Logik zu schreiben. Es vereinfacht die Redux-Entwicklung durch:

  • Reduzierung von Boilerplate-Code
  • Einbeziehung nützlicher Hilfsmittel für häufige Muster
  • Bereitstellung guter Standardeinstellungen für die Store-Einrichtung
  • Ermöglichung der direkten State-Mutation in Reducern (über Immer)

Einen Store mit configureStore erstellen

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import todosReducer from './features/todos/todosSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todosReducer
  }
});

export default store;

State-Logik mit createSlice definieren

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write ""mutating"" logic in reducers
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

Asynchrone Operationen mit createAsyncThunk handhaben

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// Create an async thunk for fetching data
export const fetchUserData = createAsyncThunk(
  'users/fetchUserData',
  async (userId, thunkAPI) => {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    return await response.json();
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
    error: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUserData.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUserData.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.data = action.payload;
      })
      .addCase(fetchUserData.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.error.message;
      });
  }
});

export default userSlice.reducer;

Praktisches Implementierungsbeispiel

Lassen Sie uns eine einfache Counter-Anwendung mit Redux Toolkit erstellen:

1. Den Store einrichten

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

2. Ein Slice für die Counter-Funktion erstellen

// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

// Selector
export const selectCount = (state) => state.counter.value;

3. Den Store mit React verbinden

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

4. Redux in einer Komponente verwenden

// features/counter/Counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  increment,
  decrement,
  incrementByAmount,
  selectCount
} from './counterSlice';

export function Counter() {
  const count = useSelector(selectCount);
  const dispatch = useDispatch();
  const [incrementAmount, setIncrementAmount] = useState('2');

  return (
    <div>
      <div>
        <button onClick={() => dispatch(decrement())}>-</button>
        <span>{count}</span>
        <button onClick={() => dispatch(increment())}>+</button>
      </div>
      <div>
        <input
          value={incrementAmount}
          onChange={(e) => setIncrementAmount(e.target.value)}
        />
        <button
          onClick={() => 
            dispatch(incrementByAmount(Number(incrementAmount) || 0))
          }
        >
          Add Amount
        </button>
      </div>
    </div>
  );
}

Techniken zur Leistungsoptimierung

Memoized Selectors mit Reselect

Reselect (in Redux Toolkit enthalten) ermöglicht es Ihnen, memoized Selector-Funktionen zu erstellen, die Ergebnisse nur neu berechnen, wenn sich Eingaben ändern:

import { createSelector } from '@reduxjs/toolkit';

// Basic selectors
const selectItems = state => state.items;
const selectFilter = state => state.filter;

// Memoized selector
export const selectFilteredItems = createSelector(
  [selectItems, selectFilter],
  (items, filter) => {
    // This calculation only runs when items or filter changes
    return items.filter(item => item.includes(filter));
  }
);

Vermeidung unnötiger Renderings

Um zu verhindern, dass Komponenten neu gerendert werden, wenn sich nicht relevante Teile des States ändern:

  1. Wählen Sie nur die spezifischen Daten aus, die Ihre Komponente benötigt
  2. Verwenden Sie memoized Selectors für abgeleitete Daten
  3. Verwenden Sie die shallowEqual-Funktion als zweites Argument für useSelector, wenn Sie Objekte auswählen
import { useSelector, shallowEqual } from 'react-redux';

// This component will only re-render when user.name or user.email changes
function UserInfo() {
  const { name, email } = useSelector(state => ({
    name: state.user.name,
    email: state.user.email
  }), shallowEqual);
  
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}

Redux vs. andere State-Management-Lösungen

Redux vs. Context API

Context API:

  • In React eingebaut
  • Einfacher für kleine bis mittlere Anwendungen
  • Keine zusätzlichen Bibliotheken erforderlich
  • Weniger Boilerplate
  • Begrenzte Debugging-Tools

Redux:

  • Leistungsfähiger für komplexe State-Logik
  • Bessere Leistung bei häufigen Updates
  • Hervorragendes Debugging mit Redux DevTools
  • Middleware-Unterstützung für Nebeneffekte
  • Strukturierterer Ansatz für State-Management

Redux vs. Zustand/Recoil/MobX

Erwägen Sie diese Alternativen, wenn:

  • Sie eine einfachere API mit weniger Boilerplate benötigen (Zustand)
  • Sie atombasiertes State-Management wünschen (Recoil)
  • Sie einen mehr objektorientierten, reaktiven Ansatz bevorzugen (MobX)

Redux ist immer noch die beste Wahl, wenn Sie Folgendes benötigen:

  • Eine ausgereifte, bewährte Lösung
  • Hervorragende Debugging-Fähigkeiten
  • Ein großes Ökosystem von Middleware und Erweiterungen
  • Vorhersehbares State-Management für komplexe Anwendungen

Fazit

Redux bietet eine leistungsstarke und vorhersehbare State-Management-Lösung für React-Anwendungen. Obwohl es eine gewisse Komplexität und Boilerplate-Code einführt, vereinfacht Redux Toolkit die Entwicklungserfahrung erheblich. Für komplexe Anwendungen mit gemeinsam genutztem State über viele Komponenten hinweg bietet Redux klare Vorteile in Bezug auf Wartbarkeit, Debugging und Vorhersehbarkeit.

Berücksichtigen Sie sorgfältig die Bedürfnisse Ihrer Anwendung bei der Auswahl einer State-Management-Lösung. Für einfachere Anwendungen könnte das eingebaute State-Management von React ausreichend sein, aber mit zunehmender Komplexität Ihrer Anwendung wird der strukturierte Ansatz von Redux immer wertvoller.

FAQs

Verwenden Sie Redux für komplexe Anwendungen mit häufigen Updates und gemeinsam genutztem State. Verwenden Sie Context für einfachere Anwendungen oder wenn Sie Werte teilen müssen, die sich nicht oft ändern.

Redux hat Kernkonzepte, die Zeit zum Verstehen benötigen könnten, aber Redux Toolkit vereinfacht die Lernkurve erheblich. Beginnen Sie mit kleinen Beispielen und steigern Sie allmählich die Komplexität.

Nicht unbedingt. Während Redux einen gewissen Overhead hinzufügt, enthält es Leistungsoptimierungen und kann tatsächlich die Leistung verbessern, indem unnötige Neurenderings verhindert werden.

Verwenden Sie Middleware wie Redux Thunk (in Redux Toolkit enthalten) oder Redux Saga für komplexere asynchrone Abläufe. Redux Toolkit's createAsyncThunk macht asynchrone Operationen unkompliziert.

Ja. Während Hooks wie useReducer und useContext einfachere State-Management-Bedürfnisse handhaben können, übertrifft Redux immer noch bei der Verwaltung komplexer Anwendungsstates, besonders in großen Teams.

Verwenden Sie die Redux DevTools Extension, um State, Actions und State-Änderungen im Zeitverlauf zu inspizieren. Es ist eines der leistungsstärksten Features von Redux.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers