Jak i kiedy używać Redux-Saga?

Zacznijmy od wyjaśnienia czym jest Redux-Saga. Jest to powszechnie znany middleware dla Reduxa. Służy on do obsługiwania efektów ubocznych, takich jak wysłanie żądania HTTP do usług zewnętrznych. Zasadniczo Saga działa jako osobny wątek dla aplikacji, który nasłuchuje konkretne akcje.

W poniższym artykule poruszymy 5 głównych zagadnień:

  • #1
    Zalety oraz wady Redux-Saga

    Czyli za i przeciw wykorzystaniu Sag w projekcie.

  • #2
    Różnice między Redux-Saga, a Redux-Thunk

    Dowiedz się, która z tych technologii będzie przydatna w twojej aplikacji.

  • #3
    Generatory w Redux-Saga

    Szybkie przypomnienie o specjalnej funkcji z Javascript.

  • #4
    Założenia i zasady działania Redux-Saga

    Poznaj historię Sag i zobacz w jakim celu zostały stworzone.

  • #5
    Implementacja Redux-Saga w projekcie

    Wprowadzenie krok po kroku jak pobrać użytkowników z API przy pomocy Sag.

Redux-Saga - middleware dla Reduxa
Redux-Saga - middleware dla Reduxa

Zalety wykorzystania Redux-Saga

Wady wykorzystania Redux-Saga

Redux-Saga vs. Redux-Thunk

Różnice między Redux-Saga, a Redux-Thunk

Przy planowaniu projektu stajesz przed wyborem biblioteki do obsługi operacji asynchronicznych.

Najpierw powinniśmy zastanowić się nad wielkością projektu.

W przypadku mniejszych projektów lepszym wyborem będzie zastosowanie Redux-Thunk, ponieważ jest łatwy do zrozumienia oraz prosty w użyciu. 

Natomiast w przypadku dużego projektu, którego logika będzie bardziej skomplikowana, lepiej wybrać Redux-Saga. Udostępnia nam on wiele przydatnych funkcji oraz pozwala utrzymać nasz kod uporządkowanym. 

Każda z tych technologii ma swoje plusy i minusy, a wybór jednej z nich zależy od potrzeb.

Czym są generatory?

Biblioteka Redux Saga zbudowana jest w oparciu o funkcje nazywane generatorami, które mogą być wstrzymywane i wznawiane.

#1

Składnia generatora

function* myFunctionGenerator() {
    //
    //
}

W przeciwieństwie do normalnej funkcji w Javascript, która działa do momentu uzyskania instrukcji return lub do całkowitego wykonania, generatory działają do momentu uzyskania instrukcji yield lub return.

#2

Działanie generatora

function* myFunctionGenerator() {
    yield 1;
    yield 2;
    return 3;
}

const generator = myFunctionGenerator();

console.log(generator.next()); // {value 1, done: false}
console.log(generator.next()); // {value 2, done: false}
console.log(generator.next()); // {value 3, done: true}

Generator zwraca nam obiekt iteracyjny. Aby przejść po obiekcie wykorzystuje się metodę next(), która zwraca następną instrukcję yield i wstrzymuje funkcję.

Yield
Służy do zwracania kolejnych wartości iteracji przez iterator (działanie podobne do return).

Metoda next()
Zwraca nam obiekt, który posiada dwie właściwości:
1. value: wartość yield,
2. done: zwraca true/false (jeśli funkcja została ukończona zwracana jest wartość true, w innym razie false).

Na czym polega koncept Redux-Saga?

Saga to sekwencja transakcji, która aktualizuje każdą usługę i publikuje komunikat lub zdarzenie wyzwalające kolejny krok transakcji.

Schemat działania Sagi
Diagram prezentujący schemat działania Redux-Saga

Generalnie Sagi nie są czymś nowym w środowisku programistycznym, ponieważ zostały wprowadzone już w 1987 roku przez Hectora Garcia-Moline i Kenneth Salem z Princeton UniversitySłużą one do obsługi długotrwałych transakcji, ze skutkami ubocznymi lub potencjalnymi niepowodzeniami.

#1

Czym są długotrwałe transakcje?

Załóżmy sytuację w której użytkownik próbuje zalogować się do naszego systemu.
Sprawdzamy czy jego nazwa użytkownika i hasło znajduję się w bazie danych. Jeśli wszystko się zgadza, zwracamy informacje wymagane we wcześniejszym żądaniu HTTP.

Opisane wyżej działania wymagają transakcji bazodanowych, które są nazywane transakcjami długotrwałymi.

#2

Jaki problem rozwiązują Sagi?

W celu odpowiedzi na pytanie, po raz kolejny posłużymy się przykładem.

Załóżmy że nasza usługa posiada wiele serwerów aplikacji.
Podczas gdy jeden z nich wykonuje pracę, inny może wejść i spróbować użyć aktualnie przetwarzanych danych. W tym momencie mamy styczność z problemem, którym jest kontrola współbieżności, a z którym sagi sobie świetnie radzą.

Implementacja Redux-Saga w projekcie

Implementacja Redux-Saga w projekcie
Implementacja Redux-Saga w projekcie

Instalacja

npm install saga-redux

Konfiguracja

Jeśli instalacja przebiegła pomyślnie możemy przejść do konfiguracji. 

#1

Importujemy Redux-Saga do naszego ‘redux/store.js’.

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./reducer";
import createSagaMiddleware from "redux-saga";
#2

Importujemy ‘rootSaga.js’, w którym będziemy przechowywać nasze sagi.

import rootSaga from "./rootSaga"
#3

Inicjujemy Sage.

const sagaMiddleware = createSagaMiddleware()
#4

Przekazujemy Sage do tablicy middlewares.

const middlewares = [sagaMiddleware]
#5

Uruchamiamy Sage, przekazując w argumencie funkcje ’rootSaga’, która zawiera wszystkie sagi.

const store = configureStore({
    reducer: {
        user: usersReducer
    },
    middleware: (getDefaultMiddleware) -> getDefaultMiddleware().concat(middlewares),
})

sagaMiddleware.run(rootSaga)

export default store
#6

Przenieśmy się teraz do pliku ‘redux/rootSaga.js’ w którym będziemy trzymać nasze sagi.

import * as types from './actionTypes';
import { takeEvery, put, all, delay, fork, call } from 'redux-saga/effects';
import {loadUsersSuccess, loadUsersError} from "./actions"
import { loadUsersApi } from './api';

function* onLoadUsersStartAsync() {
    try {
        const response = yield call(loadUsersApi);
        if(response.data) {
            yield delay(1000);
            yield put(loadUsersSuccess(response.data));
        }
    } catch (error) {
        yield put(loadUsersError(error.message));
    }
}

Utworzyliśmy tutaj funkcję, obsługującą asynchroniczne zapytanie do API, w celu pobrania użytkowników.

const response = yield call(loadUsersApi);

Za pomocą call możemy wywoływać asynchroniczne funkcje.

yield delay(1000);

Delay jest użyteczny jeśli chcemy zrobić przerwę między instrukcjami lub opóźnić ich wywołanie. Jako argument podajemy wartość w milisekundach. 

yield put(loadUsersSuccess(response.data));

Put zwraca nam obiekt z instrukcjami dla Sagi, aby wywołać daną akcję. Można uznać, że zastępuje nam dispatch.

yield put(loadUsersSuccess(response.data));
#7

Przejdźmy teraz do kolejnej funkcji, która będzie nasłuchiwać wywołanie akcji.

takeEvery – pozwala na jednoczesne uruchamianie wielu akcji.

function* onLoadUsers() {
    yield takeEvery(types.LOAD_USERS_START, onLoadUsersStartAsync);
}

Inne przydatne efekty Sagi

take
Pozwala na uruchomienie jednej akcji, następnie Saga jest zatrzymana do momentu wykonania.

takeLatest
Pozwala na działanie tylko jednej akcji. Jeśli uruchomimy tę samą akcję w trakcie trwania poprzedniej, zostanie ona natychmiast przerwana.

‘userSagas’
Przechowuje wszystkie Sagi.

const usersagas = [
    fork(onLoadUsers),
];

Fork
N
adaje efekt ‘non-blocking’, który polega na tym, że jeśli od danego zapytania do API nie można szybko otrzymać odpowiedzi, kończy się to natychmiastowym błędem. Jest to przydatne ponieważ, dzięki temu wątek nie zawiesza się.

‘rootSaga
Korzysta z efektu all, który przekazuje dla sagi informacje o tym, aby funkcje były uruchomione równolegle.

 export default function* rootSaga() {
     yield all([...userSagas]);
 }

Podsumowanie

Po przeczytaniu tego artykułu, powinno być jasne czym jest Redux-Saga oraz dlaczego wykorzystuje się go w dużych skalowalnych projektach.

Opisaliśmy wiele przydatnych efektów, które ułatwiają budowanie złożonych operacji asynchronicznych.

Podczas implementacji mogliście zauważyć, że kod jest uporządkowany i czytelny, co jest zdecydowanie dużym plusem przy pisaniu aplikacji.

Redux-Saga jest dość rozbudowaną biblioteką, więc zachęcamy do sprawdzenia jej dokumentacji.

 

Całość kodu, wraz z wykorzystanymi plikami możecie pobrać na naszym repozytorium.

Jakub Rychlicki

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

Zostaw komentarz

2 × 4 =

Top