Obecnie dużo mówi się o najnowszym dzieciaku w mieście Redux , redux-saga / redux-saga . Wykorzystuje funkcje generatora do nasłuchiwania / wysyłania akcji.
Zanim owinę głowę, chciałbym poznać zalety / wady korzystania redux-saga
zamiast podejścia poniżej, w którym korzystam redux-thunk
z asynchronizacji / czekania.
Składnik może wyglądać tak: wywołać działania jak zwykle.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
Wtedy moje działania wyglądają mniej więcej tak:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
javascript
reactjs
redux
redux-thunk
redux-saga
hampusohlsson
źródło
źródło
::
przedtemthis.onClick
?this
), aliasthis.onClick = this.onClick.bind(this)
. Dłuższa forma jest zwykle zalecana w konstruktorze, ponieważ skrót jest ponownie powiązany na każdym renderowaniu.bind()
często używająthis
tej funkcji, ale zacząłem używać() => method()
teraz.Odpowiedzi:
W redux-saga byłby to odpowiednik powyższego przykładu
Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że wywołujemy funkcje API za pomocą formularza
yield call(func, ...args)
.call
nie wykonuje efektu, po prostu tworzy zwykły obiekt podobny do{type: 'CALL', func, args}
. Wykonanie jest delegowane do oprogramowania pośredniego redux-saga, które dba o wykonanie funkcji i wznowienie działania generatora wraz z wynikiem.Główną zaletą jest to, że można przetestować generator poza Redux za pomocą prostych kontroli równości
Zauważ, że wyśmiewamy wynik wywołania interfejsu API, po prostu wstrzykując wyszydzone dane do
next
metody iteratora. Wyśmiewanie danych jest znacznie prostsze niż wyśmiewanie funkcji.Drugą rzeczą, na którą należy zwrócić uwagę, jest połączenie z
yield take(ACTION)
. Twardy są wywoływane przez twórcę akcji przy każdej nowej akcji (npLOGIN_REQUEST
.). tzn. akcje są nieustannie wypychane na kolce, a kolce nie mają kontroli nad tym, kiedy przestać je wykonywać.W Redux-sagi, generatory wyciągnąć następną akcję. tzn. mają kontrolę nad tym, kiedy słuchać jakiejś akcji, a kiedy nie. W powyższym przykładzie instrukcje przepływu są umieszczone w
while(true)
pętli, więc będzie nasłuchiwał każdej przychodzącej akcji, która nieco naśladuje zachowanie polegające na pchaniu.Podejście pull umożliwia wdrożenie złożonych przepływów sterowania. Załóżmy na przykład, że chcemy dodać następujące wymagania
Obsługuj akcję użytkownika LOGOUT
po pierwszym udanym logowaniu serwer zwraca token, który wygasa z pewnym opóźnieniem przechowywanym w
expires_in
polu. Będziemy musieli odświeżać autoryzację w tle coexpires_in
milisekundyWeź pod uwagę, że podczas oczekiwania na wynik wywołań interfejsu API (początkowe logowanie lub odświeżenie) użytkownik może się wylogować pomiędzy nimi.
Jak byś to wdrożył za pomocą thunks; zapewniając jednocześnie pełne pokrycie testowe dla całego przepływu? Oto, jak może to wyglądać w Sagas:
W powyższym przykładzie wyrażamy nasze wymaganie dotyczące współbieżności za pomocą
race
. Jeślitake(LOGOUT)
wygrywa wyścig (tj. Użytkownik kliknął przycisk wylogowania). Wyścig automatycznie anulujeauthAndRefreshTokenOnExpiry
zadanie w tle. A jeśliauthAndRefreshTokenOnExpiry
został zablokowany w środkucall(authorize, {token})
połączenia, zostanie również anulowany. Anulowanie następuje automatycznie w dół.Możesz znaleźć działające demo powyższego przepływu
źródło
delay
funkcja? Ach, znalazłem to: github.com/yelouafi/redux-saga/blob/…redux-thunk
Kod jest dość czytelny i self-wyjaśnił. Aleredux-sagas
jedno jest bardzo nieczytelny, głównie z powodu tych czasownika-jak funkcje:call
,fork
,take
,put
...Dodam swoje doświadczenie w stosowaniu sagi w systemie produkcyjnym, oprócz dość dokładnej odpowiedzi autora biblioteki.
Pro (za pomocą saga):
Testowalność Testowanie sag jest bardzo łatwe, ponieważ call () zwraca czysty obiekt. Testowanie thunks zwykle wymaga uwzględnienia mockStore w teście.
Redux-saga zawiera wiele przydatnych funkcji pomocniczych dotyczących zadań. Wydaje mi się, że koncepcja sagi polega na stworzeniu pewnego rodzaju elementu roboczego / wątku w tle dla twojej aplikacji, który działa jako brakujący element w architekturze redux reagowania (ActionCreators i reduktory muszą być czystymi funkcjami). Co prowadzi do następnego punktu.
Sagi oferują niezależne miejsce do radzenia sobie ze wszystkimi skutkami ubocznymi. Z mojego doświadczenia wynika, że zazwyczaj łatwiej jest modyfikować i zarządzać niż piorunujące akcje.
Kon:
Składnia generatora.
Wiele pojęć do nauczenia się.
Stabilność API. Wygląda na to, że redux-saga wciąż dodaje funkcje (np. Kanały?), A społeczność nie jest tak duża. Istnieje obawa, że biblioteka dokona pewnego dnia niekompatybilnej aktualizacji.
źródło
API stability
jako aktualizacji, aby odzwierciedlić obecną sytuację.Chciałbym tylko dodać kilka uwag z mojego osobistego doświadczenia (używając zarówno sag, jak i thunk):
Sagi świetnie się sprawdzają:
Sagi są silniejsze. Wszystko, co możesz zrobić w kreatorze akcji jednego Thunka, możesz także zrobić w jednej sadze, ale nie odwrotnie (a przynajmniej niełatwo). Na przykład:
take
)cancel
,takeLatest
,race
)take
,takeEvery
...)Sagas oferuje również inne przydatne funkcje, które uogólniają niektóre popularne wzorce aplikacji:
channels
słuchać zewnętrznych źródeł zdarzeń (np. websockets)fork
,spawn
)Sagi to świetne i potężne narzędzie. Jednak wraz z mocą wiąże się odpowiedzialność. Gdy Twoja aplikacja rośnie, możesz łatwo zgubić się, zastanawiając się, kto czeka na akcję do wysłania lub co się stanie, gdy akcja zostanie wysłana. Z drugiej strony thunk jest prostszy i łatwiejszy do uzasadnienia. Wybór jednego lub drugiego zależy od wielu aspektów, takich jak rodzaj i rozmiar projektu, jakie rodzaje skutków ubocznych Twój projekt musi obsłużyć lub preferencje zespołu programistów. W każdym razie po prostu utrzymaj swoją aplikację prostą i przewidywalną.
źródło
Po prostu osobiste doświadczenie:
Jeśli chodzi o styl kodowania i czytelność, jedną z najważniejszych zalet korzystania z redux-sagi w przeszłości jest unikanie piekła zwrotnego w redux-thunk - nie trzeba już używać wielu zagnieżdżeń. Ale teraz, gdy popularność async / czekaj na thunk, można również napisać kod asynchroniczny w stylu synchronizacji, używając redux-thunk, co można uznać za poprawę w myśleniu redux.
Podczas korzystania z redux-sagi może być konieczne napisanie o wiele więcej kodu, szczególnie w maszynopisie. Na przykład, jeśli chce się wdrożyć funkcję asynchronizacji pobierania, obsługę danych i błędów można wykonać bezpośrednio w jednej jednostce Thunk w pliku Action.js za pomocą jednej akcji FETCH. Ale w redux-saga może być konieczne zdefiniowanie akcji FETCH_START, FETCH_SUCCESS i FETCH_FAILURE oraz wszystkich powiązanych kontroli typu, ponieważ jedną z funkcji w redux-sadze jest użycie tego rodzaju bogatego mechanizmu „tokenów” do tworzenia efektów i instruowania sklep redux do łatwego testowania. Oczywiście można napisać sagę bez użycia tych działań, ale to by sprawiło, że byłaby podobna do bzdury.
Jeśli chodzi o strukturę plików, saga redux wydaje się być bardziej wyraźna w wielu przypadkach. Można łatwo znaleźć kod asynchroniczny w każdym pliku sagas.ts, ale w redux-thunk trzeba by go zobaczyć w działaniach.
Łatwe testowanie może być kolejną ważoną funkcją w redux-sadze. To jest naprawdę wygodne. Ale jedną rzeczą, którą należy wyjaśnić, jest to, że test „wywołania” redux-saga nie wykonałby rzeczywistego wywołania API podczas testowania, dlatego należałoby określić przykładowy wynik dla kroków, które mogą go wykorzystać po wywołaniu API. Dlatego przed napisaniem redagi-sagi lepiej byłoby dokładnie zaplanować sagę i odpowiadające jej sagas.spec.ts.
Redux-saga zapewnia również wiele zaawansowanych funkcji, takich jak równoległe uruchamianie zadań, pomocniki współbieżności, takie jak takeLatest / takeEvery, fork / spawn, które są znacznie potężniejsze niż thunks.
Podsumowując, osobiście chciałbym powiedzieć: w wielu normalnych przypadkach i małych i średnich aplikacjach używaj asynchronicznego / oczekującego stylu redux-thunk. Pozwoliłoby to zaoszczędzić wiele kodów / akcji / typedefs i nie musiałbyś przełączać wielu różnych sagas.ts i utrzymywać określonego drzewa sagas. Ale jeśli tworzysz dużą aplikację o bardzo złożonej logice asynchronicznej i potrzebujesz takich funkcji, jak współbieżność / wzorzec równoległy lub masz duże zapotrzebowanie na testowanie i konserwację (szczególnie w rozwoju opartym na testach), sagi redux prawdopodobnie uratują ci życie .
W każdym razie saga redux nie jest trudniejsza i bardziej złożona niż sama redux i nie ma tak zwanej krzywej uczenia się, ponieważ ma dobrze ograniczone podstawowe koncepcje i interfejsy API. Poświęcenie niewielkiej ilości czasu na naukę sagi redux może przynieść korzyści w przyszłości.
źródło
Po zapoznaniu się z kilkoma różnymi projektami React / Redux na dużą skalę z mojego doświadczenia, Sagas zapewnia programistom bardziej uporządkowany sposób pisania kodu, który jest znacznie łatwiejszy do przetestowania i trudniejszy do popełnienia błędu.
Tak, to trochę dziwne na początek, ale większość deweloperów ma dość zrozumienia tego w ciągu jednego dnia. Zawsze mówię ludziom, żeby się nie martwili o co
yield
zacząć, i że po napisaniu kilku testów przyjdzie do ciebie.Widziałem kilka projektów, w których gromady traktowano tak, jakby były kontrolerami z modelu MVC, a to szybko staje się nie do utrzymania.
Radzę używać Sag, gdzie potrzebujesz A wyzwala rzeczy typu B odnoszące się do pojedynczego zdarzenia. W przypadku czegokolwiek, co mogłoby przecinać wiele akcji, uważam, że łatwiej jest napisać oprogramowanie pośrednie klienta i użyć właściwości meta akcji FSA do jej uruchomienia.
źródło
Thunks vs. Sagas
Redux-Thunk
iRedux-Saga
różnią się pod kilkoma ważnymi względami, obie są bibliotekami oprogramowania pośredniego dla Redux (oprogramowanie pośrednie Redux to kod, który przechwytuje działania przychodzące do sklepu za pomocą metody dispatch ()).Akcja może być dosłownie czymkolwiek, ale jeśli przestrzegasz najlepszych praktyk, akcja jest zwykłym obiektem javascript z polem typu i opcjonalnymi polami ładunku, meta i błędów. na przykład
Redux-Thunk
Oprócz wysyłania standardowych działań
Redux-Thunk
oprogramowanie pośrednie umożliwia wysyłanie specjalnych funkcji, zwanychthunks
.Thunks (w Redux) ogólnie mają następującą strukturę:
Oznacza to, że a
thunk
jest funkcją, która (opcjonalnie) przyjmuje niektóre parametry i zwraca inną funkcję. Funkcja wewnętrzna przyjmuje funkcjędispatch function
igetState
- obie będą dostarczane przezRedux-Thunk
oprogramowanie pośrednie.Redux-Saga
Redux-Saga
oprogramowanie pośrednie pozwala wyrazić złożoną logikę aplikacji jako czyste funkcje zwane sagami. Czyste funkcje są pożądane z punktu widzenia testowania, ponieważ są przewidywalne i powtarzalne, co czyni je stosunkowo łatwymi do testowania.Sagi są realizowane za pomocą specjalnych funkcji zwanych funkcjami generatora. Są to nowe funkcje
ES6 JavaScript
. Zasadniczo wykonanie wskakuje i wychodzi z generatora wszędzie tam, gdzie widać instrukcję dochodu. Pomyśl oyield
stwierdzeniu, które powoduje zatrzymanie generatora i zwrócenie uzyskanej wartości. Później osoba dzwoniąca może wznowić działanie generatora na wyciągu poyield
.Funkcja generatora to taka zdefiniowana w ten sposób. Zwróć uwagę na gwiazdkę po słowie kluczowym funkcji.
Po zarejestrowaniu sagi logowania
Redux-Saga
. Ale wtedyyield
przyjęcie pierwszego wiersza zatrzyma sagę, dopóki akcja z typem nie'LOGIN_REQUEST'
zostanie wysłana do sklepu. Gdy to nastąpi, wykonywanie będzie kontynuowane.Aby uzyskać więcej informacji, zobacz ten artykuł .
źródło
Jedna szybka uwaga. Generatory można anulować, asynchronicznie / oczekuj - nie. Na przykład z pytania tak naprawdę nie ma sensu, co wybrać. Jednak w przypadku bardziej skomplikowanych przepływów czasami nie ma lepszego rozwiązania niż użycie generatorów.
Innym pomysłem może być użycie generatorów z redux-thunk, ale dla mnie wygląda to na próbę wynalezienia roweru z kwadratowymi kołami.
I oczywiście generatory są łatwiejsze do przetestowania.
źródło
Oto projekt, który łączy w sobie najlepsze elementy (plusy) obu
redux-saga
iredux-thunk
: można obsługiwać wszystkie skutki uboczne na sag podczas uzyskiwania przyrzeczenia przezdispatching
odpowiedniego działania: https://github.com/diegohaz/redux-saga-thunkźródło
then()
wewnątrz komponentu React jest sprzeczne z paradygmatem. Powinieneś poradzić sobie ze zmienionym stanem,componentDidUpdate
zamiast czekać na rozwiązanie obietnicy.componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
Łatwiejszym sposobem jest użycie redux-auto .
z dokumantacji
Chodzi o to, aby każda akcja znajdowała się w określonym pliku . kolokacja wywołania serwera w pliku z funkcjami reduktora dla „oczekujących”, „spełnionych” i „odrzuconych”. To sprawia, że obsługa obietnic jest bardzo łatwa.
Automatycznie dołącza również obiekt pomocnika (zwany „asynchronizacją”) do prototypu stanu, umożliwiając śledzenie w interfejsie użytkownika żądanych przejść.
źródło