Asynchroniczność w Redux

Na ten moment asynchroniczność w aplikacjach wydaje się być nierozłączna. Dlatego podczas korzystania z Redux, decydujemy się na wybór middleware, który pomoże nam obsłużyć tak zwane “side effects”.

Może to być na przykład:

  • zapisywanie pliku,
  • ustawienie asynchronicznego stopera,
  • wysłanie żądania HTTP.

Aktualnie do wyboru mamy dwa najpopularniejsze middleware, które pozwalają nam na tworzenie asynchronicznych akcji: Redux-Saga oraz Redux-Thunk, który poznamy lepiej w poniższym artykule.

Czym jest Redux-Thunk?

Jest to middleware, którego celem jest ułatwienie nam dodania pewnej logiki lub uruchomienie kodu asynchronicznego przed wysłaniem akcji. Pozwala nam napisać funkcję, która jako argument może przyjmować dispatch oraz getState. 

dispatch – jest to metoda służąca do wysyłania akcji, które mogą zostać odebrane przez reducer.

getState – pozwala na uzyskanie danych ze store.

Co oznacza słowo “thunk”?

Definicyjnie thunk oznacza:

fragment kodu, który wykonuje pewną opóźnioną pracę.

Inaczej jest to określenie funkcji, która zwraca inną funkcję. W pewnym sensie opóźnia ona wywołanie funkcji zwracanej o określony przedział czasu.

Asynchroniczny przepływ danych w Redux

Na samym starcie musimy obsłużyć zdarzenie użytkownika w aplikacji, takie jak kliknięcie przycisku. Następnie wywołujemy dispatch().

Gdy rozpoczęta akcja dotrze do middleware, może wykonać działanie asynchroniczne, a po zakończeniu wysłać rzeczywisty obiekt akcji.

Podczas dodawania logiki asynchronicznej do aplikacji Redux, wykonujemy dodatkowy krok, dzięki któremu middleware będzie mógł wysłać żądanie HTTP do API.

Powyższy schemat przedstawia asynchroniczny przepływ danych w Redux.

Przykładowy projekt z wykorzystaniem Redux-Thunk

Na potrzeby artykułu stworzymy prostą aplikację React, Redux + Redux-Thunk, która będzie wyświetlała filmy. Do tego celu skorzystamy z API ze strony https://www.themoviedb.org/.

#1

Instalacja Redux-Thunk w projekcie

npm install redux-thunk
#2

Dodanie middleware do Reduxa

  • ./store/index.js
import { configureStore } from '@reduxjs/toolkit';
import moviesReducer from './reducers/movies';
import ReduxThunk from 'redux-thunk';
 
const middlewares = [ReduxThunk];
 
const store = configureStore({
  reducer: {
    movies: moviesReducer,
  },
  middleware: middlewares,
});
 
export default store;
#3

Tworzenie typów dla różnych stanów akcji dla pobierania filmów

  • ./store/actions/actionTypes.js
./store/actions/actionTypes.js

export const FETCH_MOVIES_START = 'FETCH_MOVIES_START';
export const FETCH_MOVIES_SUCCESS = 'FETCH_MOVIES_SUCCESS';
export const FETCH_MOVIES_ERROR = 'FETCH_MOVIES_ERROR';
#4

Podstawowe akcje dla napisanych wcześniej stanów

  • ./store/actions/movies.js
import * as actionTypes from './actionTypes';
 
export const fetchMoviesStart = () => {
  return {
    type: actionTypes.FETCH_MOVIES_START,
  };
};
export const fetchMoviesSuccess = (movies) => {
  return {
    type: actionTypes.FETCH_MOVIES_SUCCESS,
    payload: movies,
  };
};
 
export const fetchMoviesFail = (error) => {
  return {
    type: actionTypes.FETCH_MOVIES_ERROR,
    error: error,
  };
};
#5

Thunk, który działa tak samo jak zwykła akcja

Nadal wysyłamy wartość zwracaną, ale tym razem zamiast obiektu zwraca nam ona funkcję.

Początkowo wywołujemy funkcję dispatch(fetchMoviesStart()) odpowiadającą za rozpoczęcie ładowania się filmów. 

Następnie obsługujemy asynchroniczne zapytanie do API, aby pozyskać filmy. Przy użyciu dispatch(fetchMoviesSuccess(movies.results)) przekazujemy dane do store.

setTimeout służy nam do opóźnienia wywołania akcji w celu pokazania efektu ładowania się na interfejsie.

W przypadku wystąpienia błędu wykonuje się dispatch(fetchMoviesFail(error). Za pomocą tej akcji pozyskujemy informacje o błędzie.

Dodatkowo niezależnie czy zapytanie do API się powiedzie, przerywamy ładowanie.

  • ./store/actions/movies.js
import * as actionTypes from './actionTypes';
 
[...]
 
export const fetchMovies = () => {
  return async (dispatch) => {
    dispatch(fetchMoviesStart());
 
    try {
      const apiKey = 'YOUR API KEY';
      const data = await fetch(`https://api.themoviedb.org/3/movie/popular?api_key=${apiKey}`);
      const movies = await data.json();
 
      setTimeout(() => {
        dispatch(fetchMoviesSuccess(movies.results));
      }, 1000);
    } catch (error) {
      dispatch(fetchMoviesFail(error));
    }
  };
};
#6

Konfiguracja reducera

  • ./store/reducers/movies.js
import * as actionTypes from '../actions/actionTypes';
 
const initialState = {
  isLoading: false,
  movies: null,
  error: null,
};
 
const moviesReducer = (state = initialState, action) => {
  switch (action.type) {
    case actionTypes.FETCH_MOVIES_START:
      return {
        ...state,
        isLoading: true,
      };
    case actionTypes.FETCH_MOVIES_SUCCESS:
      return {
        ...state,
        isLoading: false,
        movies: action.payload,
      };
    case actionTypes.FETCH_MOVIES_ERROR:
      return {
        ...state,
        isLoading: false,
        error: action.payload,
      };
    default:
      return state;
  }
};
 
export default moviesReducer;
#7

Prezentacja aplikacji

Podsumowanie

Redux-Thunk to świetne rozwiązanie dla niewielkich aplikacji, które potrzebują narzędzia do obsługi operacji asynchronicznych. Jest to doskonały wybór dla osób dopiero uczących się Redux z uwagi na jego prostą implementację. Gdy już poczujesz się z nim komfortowo, dobrym pomysłem jest wypróbowanie alternatyw takich jak Redux-Saga czy Redux-Observable.

Szukasz pracy, w której możesz rozwijać swoje zainteresowania i na bieżąco zdobywać nowe umiejętności? Sprawdź nasze oferty!

Podobało się?
Jakub Rychlicki

Nowicjusz w świecie React Native. Zwolennik prostych i minimalistycznych rozwiązań.

Zostaw komentarz

4 × 5 =

Top