Plusy / minusy używania redux-saga z generatorami ES6 vs redux-thunk z ASYNCHRONIĄ ES2017 /
Dużo się teraz mówi o najnowszym dzieciaku w mieście redux, redux-saga / redux-saga . Wykorzystuje funkcje generatora do nasłuchiwania / wysyłania akcji.
Zanim się tym zajmę, chciałbym poznać plusy/minusy używania redux-saga
zamiast poniższego podejścia, gdzie używam redux-thunk
z asynchronią/oczekiwaniem.
Komponent może wyglądać tak, 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ą jak to:
// 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...
7 answers
W redux-saga odpowiednikiem powyższego przykładu będzie
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
Pierwszą rzeczą, którą należy zauważyć, jest to, że wywołujemy funkcje api za pomocą formularza yield call(func, ...args)
. call
nie wykonuje efektu, po prostu tworzy zwykły obiekt, taki jak {type: 'CALL', func, args}
. Wykonanie jest delegowane do oprogramowania pośredniczącego redux-saga, które zajmuje się wykonaniem funkcji i wznowieniem generatora z jej wynikiem.
Główną zaletą jest to, że można przetestować generator poza Redux za pomocą prostego kontrole równości
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
Uwaga wyśmiewamy wynik wywołania api, po prostu wstrzykując wyśmiewane dane do metody next
iteratora. Wyśmiewanie danych jest o wiele prostsze niż wyśmiewanie funkcji.
yield take(ACTION)
. Thunks są wywoływane przez twórcę akcji przy każdej nowej akcji (np. LOGIN_REQUEST
). tzn. akcje są nieustannie popychane do thunksa, a thunks nie ma kontroli, kiedy przestać je wykonywać.
W redux-saga, Generatory pociągnij następną akcję. tj. mają kontrolę kiedy słuchać jakiejś akcji, a kiedy nie. W powyższym przykładzie instrukcje przepływu są umieszczone wewnątrz pętli while(true)
, więc będzie ona nasłuchiwać każdej nadchodzącej akcji, która w pewien sposób naśladuje zachowanie pchania thunk.
Podejście pull umożliwia implementację złożonych przepływów sterowania. Załóżmy na przykład, że chcemy dodać następujące wymagania
-
Obsługa logowania użytkownika akcja
-
Po pierwszym pomyślnym zalogowaniu serwer zwraca token, który wygasa z pewnym opóźnieniem przechowywanym w polu
expires_in
. Będziemy musieli odświeżyć autoryzację w tle na każdąexpires_in
milisekundę Należy wziąć pod uwagę, że w oczekiwaniu na wynik wywołań api (początkowego logowania lub odświeżania) użytkownik może wylogować się pomiędzy nimi.
Jak zaimplementowałbyś to z thunksem; jednocześnie zapewniając pełny zakres testów dla cały przepływ? Oto jak to może wyglądać z Sagami:
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
W powyższym przykładzie wyrażamy nasze wymagania dotyczące współbieżności za pomocą race
. Jeśli take(LOGOUT)
wygra wyścig (tzn. użytkownik kliknął przycisk wylogowania). Wyścig automatycznie anuluje authAndRefreshTokenOnExpiry
zadanie w tle. A jeśli {[14] } został zablokowany w środku call(authorize, {token})
połączenie zostanie również anulowane. Anulowanie propaguje się automatycznie w dół.
Można znaleźć runnable demo powyższego przepływu
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-11 15:50:25
Dodam moje doświadczenie z wykorzystaniem sagi w systemie produkcyjnym do dość dokładnej odpowiedzi autora biblioteki.
Pro (using saga):
Testowalność. Bardzo łatwo jest przetestować sagi, ponieważ call () zwraca czysty obiekt. Testowanie thunksa zwykle wymaga włączenia mockStore do testu.
Redux - saga zawiera wiele przydatnych funkcji pomocniczych dotyczących zadań. Wydaje mi się, że koncepcja sagi polega na stworzeniu pewnego rodzaju tła worker / thread dla Twojej aplikacji, która działa jak brakujący element w architekturze React redux (actioncreatory i reduktory muszą być czystymi funkcjami.), Co prowadzi do następnego punktu.
Sagi oferują niezależne miejsce do obsługi wszystkich skutków ubocznych. Zazwyczaj łatwiej jest modyfikować i zarządzać niż thunk akcji w moim doświadczeniu.
Con:
Składnia generatora.
Wiele pojęć do nauki.
Stabilność API. Wydaje się, że redux-saga jest nadal dodawanie funkcji(np. kanały?), a społeczność nie jest tak duża. Istnieje obawa, czy biblioteka pewnego dnia dokona nie zgodnej wstecz aktualizacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-22 16:20:05
Chciałbym tylko dodać kilka komentarzy z mojego osobistego doświadczenia (używając zarówno sagi, jak i thunk):
Sagi świetnie sprawdzają się:
- nie musisz naśladować funkcji owiniętych efektami
- dlatego testy są czyste, czytelne i łatwe do napisania
- podczas używania sagi twórcy akcji zwracają najczęściej zwykłe literały obiektów. Łatwiej jest również przetestować i potwierdzić w przeciwieństwie do obietnic thunka.
- oczekiwanie na wysłanie akcji/akcji (
take
) - anuluj istniejącą rutynę (
cancel
,takeLatest
,race
) - wiele procedur może słuchać tej samej akcji (
take
,takeEvery
, ...)
Sagas oferuje również inne przydatne funkcje, które uogólniają niektóre popularne wzorce aplikacji:
-
channels
aby słuchać zewnętrznych źródeł zdarzeń (np. websockets) - model widelca (
fork
,spawn
) - przepustnica
- ...
Sagi są wielkim i potężnym narzędziem. Jednak z mocą przychodzi odpowiedzialność. Gdy Twoja aplikacja rośnie, możesz łatwo stracić, zastanawiając się, kto czeka na wysłanie akcji lub co się dzieje, gdy jakaś akcja jest wysyłana. Z drugiej strony thunk jest prostszy i łatwiejszy do rozumowania. Wybór jednego lub drugiego zależy od wielu aspektów, takich jak typ i rozmiar projektu, jakie rodzaje efektów ubocznych musi obsługiwać Twój projekt lub preferencje zespołu deweloperskiego. W każdym razie po prostu zachowaj prostą i przewidywalną aplikację.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-29 08:58:21
Po przejrzeniu kilku różnych dużych projektów React/Redux w moim doświadczeniu, Saga zapewniają programistom bardziej ustrukturyzowany sposób pisania kodu, który jest o wiele łatwiejszy do przetestowania i trudniejszy do pomyłki.
Tak, to trochę dziwne, ale większość programistów ma dość zrozumienia tego w jeden dzień. Zawsze mówię ludziom, aby nie martwili się o to, coyield
robi na początek i że jak napiszesz kilka testów to przyjdzie do ciebie.
Widziałem kilka projektów gdzie thunksy zostały potraktowane tak, jakby były kontrolerami z MVC patten i to szybko staje się niezniszczalnym bałaganem.
Moja rada to użycie sagi, w której potrzebujesz wyzwalacza typu B, związanego z pojedynczym wydarzeniem. W przypadku wszystkiego, co mogłoby przeciąć wiele akcji, łatwiej jest napisać oprogramowanie pośredniczące klienta i użyć meta właściwości akcji FSA do jej uruchomienia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-14 21:04:03
Oto projekt, który łączy najlepsze części (plusy) zarówno redux-saga
jak i redux-thunk
: możesz poradzić sobie ze wszystkimi efektami ubocznymi na sagach, otrzymując obietnicę przez dispatching
odpowiednią akcję:
https://github.com/diegohaz/redux-saga-thunk
class MyComponent extends React.Component {
componentWillMount() {
// `doSomething` dispatches an action which is handled by some saga
this.props.doSomething().then((detail) => {
console.log('Yaay!', detail)
}).catch((error) => {
console.log('Oops!', error)
})
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 03:39:00
Łatwiejszym sposobem jest użycie redux-auto .
Z dokumentu
Redux-auto naprawił ten problem asynchroniczny, pozwalając na utworzenie funkcji "action", która zwraca obietnicę. Aby towarzyszyć" domyślnej " logiki działania funkcji.
- nie ma potrzeby stosowania innego oprogramowania pośredniczącego Redux asynchronicznego. np. thunk, promise-middleware, saga
- łatwo pozwala przekazać obietnicę do redux i mieć ją za ty
- pozwala na wspólne lokalizowanie połączeń zewnętrznych z miejscem, w którym zostaną przekształcone
- nazwanie pliku " init.js " wywoła go raz przy starcie aplikacji. Jest to dobre do ładowania danych z serwera przy starcie
Chodzi o to, aby każda akcja była umieszczona w konkretnym pliku . współlokowanie wywołania serwera w pliku z funkcjami "oczekujące", "spełnione" i "odrzucone". To sprawia, że obsługa obietnic jest bardzo łatwa.
To również automatycznie dołącza obiekt pomocniczy (zwany "async") do prototypu twojego stanu, umożliwiając śledzenie w interfejsie użytkownika żądanych przejść.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-24 13:25:10
Jedna szybka nuta. Generatory można anulować, asynchroniczne / oczekujące-nie. Więc dla przykładu z pytania, to naprawdę nie ma sensu, co wybrać. Ale dla bardziej skomplikowanych przepływów czasami nie ma lepszego rozwiązania niż korzystanie z generatorów.
Innym pomysłem może być użycie generatorów z redux-thunk, ale dla mnie wygląda to na próbę wymyślenia roweru z kwadratowymi kołami.
I oczywiście generatory są łatwiejsze do przetestowania.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-14 22:11:29