Ty nie.
Ale ... powinieneś użyć redux-saga :)
Odpowiedź Dana Abramova jest słuszna, redux-thunk
ale powiem trochę więcej o sadze redux, która jest dość podobna, ale mocniejsza.
Tryb rozkazujący a deklaratywny
- DOM : jQuery jest konieczny / React jest deklaratywny
- Monady : IO jest konieczne / Free jest deklaratywne
- Efekty dodatkowe :
redux-thunk
jest konieczny / redux-saga
deklaratywny
Kiedy masz w ręku kutasa, jak monada IO lub obietnica, nie możesz łatwo wiedzieć, co zrobi po wykonaniu. Jedynym sposobem przetestowania thunk jest wykonanie go i wyśmiewanie się z dyspozytora (lub całego świata zewnętrznego, jeśli wchodzi on w interakcje z większą ilością rzeczy ...).
Jeśli korzystasz z prób, nie wykonujesz programowania funkcjonalnego.
Widziane przez pryzmat efektów ubocznych, kpiny są flagą, że kod jest nieczysty, aw oczach funkcjonalnego programisty dowód, że coś jest nie tak. Zamiast pobierać bibliotekę, która pomoże nam sprawdzić, czy góra lodowa jest nienaruszona, powinniśmy żeglować wokół niej. Ostry facet TDD / Java zapytał mnie kiedyś, jak robisz drwiny w Clojure. Odpowiedź brzmi: zwykle nie. Zwykle postrzegamy to jako znak, że musimy zmienić kod naszego kodu.
Źródło
Sagi (po ich zaimplementowaniu redux-saga
) są deklaratywne i podobnie jak komponenty Free monad lub React, są znacznie łatwiejsze do przetestowania bez żadnego próbnego.
Zobacz także ten artykuł :
we współczesnym FP nie powinniśmy pisać programów - powinniśmy pisać opisy programów, które możemy następnie introspekcjonować, przekształcać i interpretować do woli.
(W rzeczywistości saga Redux jest jak hybryda: przepływ jest konieczny, ale efekty są deklaratywne)
Zamieszanie: akcje / zdarzenia / polecenia ...
W świecie frontendu istnieje wiele nieporozumień co do tego, w jaki sposób niektóre koncepcje backendu, takie jak CQRS / EventSourcing i Flux / Redux, mogą być powiązane, głównie dlatego, że w Flux używamy terminu „akcja”, który może czasami reprezentować zarówno imperatywny kod ( LOAD_USER
), jak i zdarzenia ( USER_LOADED
). Uważam, że podobnie jak pozyskiwanie wydarzeń, powinieneś wysyłać tylko zdarzenia.
Wykorzystywanie sag w praktyce
Wyobraź sobie aplikację z linkiem do profilu użytkownika. Idiomatycznym sposobem radzenia sobie z tym z każdym oprogramowaniem pośrednim byłoby:
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
Ta saga przekłada się na:
za każdym razem, gdy użytkownik kliknie nazwę użytkownika, pobierz profil użytkownika, a następnie wywołaj zdarzenie z załadowanym profilem.
Jak widać, istnieją pewne zalety redux-saga
.
Użycie takeLatest
zezwoleń na wyrażenie, że jesteś zainteresowany jedynie uzyskaniem danych o ostatniej klikniętej nazwie użytkownika (radzisz sobie z problemami dotyczącymi współbieżności w przypadku, gdy użytkownik kliknie bardzo szybko wiele nazw użytkowników). Tego rodzaju rzeczy są trudne do zrobienia. Możesz użyć, takeEvery
jeśli nie chcesz tego zachowania.
Dbasz o czystość twórców akcji. Pamiętaj, że nadal warto zachować ActionCreators (w sagach put
i komponentach dispatch
), ponieważ może to pomóc w dodaniu sprawdzania poprawności akcji (asercje / przepływ / maszynopis) w przyszłości.
Twój kod staje się znacznie bardziej testowalny, ponieważ efekty są deklaratywne
Już nie potrzebujesz, aby wywoływać połączenia podobne do RPC actions.loadUser()
. Twój interfejs użytkownika musi tylko wysłać to, co się stało. Odpalamy tylko zdarzenia (zawsze w czasie przeszłym!), A nie akcje. Oznacza to, że możesz tworzyć oddzielone „kaczki” lub ograniczone konteksty i że saga może działać jako punkt sprzężenia między tymi modułowymi elementami.
Oznacza to, że Twoje widoki są łatwiejsze do zarządzania, ponieważ nie muszą już zawierać tej warstwy tłumaczenia między tym, co się wydarzyło, a tym, co powinno się stać efektem
Na przykład wyobraź sobie nieskończony widok przewijania. CONTAINER_SCROLLED
może prowadzić do NEXT_PAGE_LOADED
, ale czy tak naprawdę to na przewijanym kontenerze spoczywa decyzja, czy należy załadować inną stronę? Następnie musi zdawać sobie sprawę z bardziej skomplikowanych rzeczy, takich jak to, czy ostatnia strona została pomyślnie załadowana, czy jest już strona, która próbuje się załadować, czy też nie ma już więcej elementów do załadowania? Nie sądzę: dla maksymalnej możliwości wielokrotnego użytku przewijany pojemnik powinien po prostu opisać, że został przewinięty. Ładowanie strony jest „efektem biznesowym” tego zwoju
Niektórzy mogą argumentować, że generatory mogą z natury ukrywać stan poza magazynem redux za pomocą zmiennych lokalnych, ale jeśli zaczniesz aranżować skomplikowane rzeczy wewnątrz thunks poprzez uruchomienie timerów itp., I tak będziesz miał ten sam problem. I istnieje select
efekt, który pozwala teraz uzyskać pewien stan ze sklepu Redux.
Sagi można podróżować w czasie, a także umożliwia złożone rejestrowanie przepływu i narzędzia deweloperskie, nad którymi obecnie pracujemy. Oto kilka prostych zapisów przepływu asynchronicznego, które zostały już zaimplementowane:
Oddzielenie
Sagi nie tylko zastępują redux thunks. Pochodzą z backendu / systemów rozproszonych / pozyskiwania zdarzeń.
Bardzo powszechne jest błędne przekonanie, że sagi są tutaj po to, aby zastąpić twoje redukcje lepszymi testami. W rzeczywistości jest to tylko szczegół implementacji redux-sagi. Użycie efektów deklaratywnych jest lepsze niż testowanie, ale wzorzec sagi można zaimplementować na podstawie kodu rozkazującego lub deklaratywnego.
Po pierwsze, saga to oprogramowanie, które pozwala na koordynację długo trwających transakcji (ostateczna spójność) i transakcji w różnych kontekstach (żargon projektowy oparty na domenie).
Aby uprościć to dla frontendowego świata, wyobraź sobie, że jest widget1 i widget2. Kliknięcie przycisku na widżecie1 powinno mieć wpływ na widżet2. Zamiast łączyć dwa widżety razem (tj. Widget1 wywołuje akcję skierowaną na widget2), widget1 wywołuje tylko kliknięcie jego przycisku. Następnie saga nasłuchuje kliknięcia tego przycisku, a następnie aktualizuje widget2, usuwając nowe zdarzenie, o którym widget2 jest świadomy.
Dodaje to poziomu pośrednictwa, który nie jest konieczny w przypadku prostych aplikacji, ale ułatwia skalowanie złożonych aplikacji. Teraz możesz publikować widget1 i widget2 w różnych repozytoriach npm, aby nigdy nie musieli się o sobie znać bez konieczności udostępniania im globalnego rejestru działań. Dwa widżety są teraz ograniczonymi kontekstami, które mogą żyć osobno. Nie muszą się nawzajem zachowywać spójności i mogą być ponownie wykorzystywane w innych aplikacjach. Saga jest punktem połączenia między dwoma widżetami, które koordynują je w znaczący sposób dla Twojej firmy.
Kilka ciekawych artykułów na temat struktury aplikacji Redux, na których możesz używać sagi Redux z powodów oddzielania:
Konkretny przypadek użycia: system powiadomień
Chcę, aby moje komponenty mogły wyświetlać powiadomienia w aplikacji. Ale nie chcę, aby moje komponenty były silnie sprzężone z systemem powiadomień, który ma własne reguły biznesowe (maks. 3 powiadomienia wyświetlane jednocześnie, kolejkowanie powiadomień, czas wyświetlania 4 sekundy itp.).
Nie chcę, aby moje komponenty JSX decydowały, kiedy powiadomienie zostanie wyświetlone / ukryte. Po prostu daję mu możliwość zażądania powiadomienia i pozostawienie skomplikowanych zasad w sadze. Tego rodzaju rzeczy są dość trudne do zrealizowania za pomocą gromad lub obietnic.
Opisałem tutaj, jak można to zrobić za pomocą sagi
Dlaczego nazywa się Saga?
Termin saga pochodzi ze świata zaplecza. Początkowo wprowadziłem do tego terminu Yassine (autor redagi-sagi) w długiej dyskusji .
Początkowo termin ten został wprowadzony w formie papierowej , wzorzec sagi miał być używany do obsługi ostatecznej spójności w transakcjach rozproszonych, ale jego użycie zostało rozszerzone na szerszą definicję przez programistów backendu, tak że teraz obejmuje również „menedżera procesów” wzorzec (w jakiś sposób oryginalny wzorzec sagi jest wyspecjalizowaną formą menedżera procesów).
Dzisiaj termin „saga” jest mylący, ponieważ może opisać 2 różne rzeczy. Używany w sadze redux nie opisuje sposobu obsługi transakcji rozproszonych, ale raczej sposób koordynacji działań w Twojej aplikacji. redux-saga
można by również nazwać redux-process-manager
.
Zobacz też:
Alternatywy
Jeśli nie podoba ci się pomysł użycia generatorów, ale interesuje Cię wzorzec sagi i jego właściwości oddzielające, możesz również osiągnąć to samo z obserwowalnym redux, który używa nazwy epic
do opisania dokładnie tego samego wzorca, ale z RxJS. Jeśli znasz już Rx, poczujesz się jak w domu.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Niektóre przydatne sagi redux
2017 r
- Nie nadużywaj sagi Redux tylko ze względu na jej użycie. Tylko sprawdzalne wywołania API nie są tego warte.
- Nie usuwaj grudek z twojego projektu w najprostszych przypadkach.
- Nie wahaj się wysyłać thunksów,
yield put(someActionThunk)
jeśli ma to sens.
Jeśli boisz się używać sagi Redux (lub obserwowalnej w Redux), ale potrzebujesz jedynie wzoru oddzielenia, sprawdź redux-dispatch-subscribe : pozwala na słuchanie wysyłek i wyzwalanie nowych wysyłek w słuchaczu.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});