Redux hoje em dia — De Action Creators até Sagas | by Eduardo Rabelo | Medium
Um modo simples e declarativo de cuidar dos seus side-effects!
Redux hoje em dia — De Action Creators até Sagas
Mar 5, 2017·5 min read
Eu uso uma expressão toda vez que eu encontro algo, relacionado a tecnologia, que eu sei que vai mudar meu jeito de desenvolver:
P*&# que pariu! Isso é irado!
E quer saber, isso aconteceu a um tempo atrás. E foi onde eu descobri o redux-saga, e eu falei: "P*&# que pariu! Isso é irado! É exatamente o que eu estava procurando".
Se você não conhece muito sobre Redux, eu recomendo você ler sobre ele nos meus outros artigos (nesse link e nesse também), antes de ir adiante! A partir daqui, eu espero que você entenda como uma aplicação Redux funcione.
Era a parte que faltava em aplicações front-end?
Uma aplicação front-end usando uma arquitetura com Redux, pode ser descrita como:
Uma global store guardando o estado imutável da aplicação.
Esse estado é renderizado em componentes (HTML ou qualquer outra coisa). É uma simples e testável (nem sempre :P) função pura.
const render = (state) => components
3. Componentes que podem despachar ações para a global store.
4. O estado é alterado usando reducers que são simples funções puras.
const reducer = (oldState, action) => newState
5. Volte para o item 1.
Olhando esses itens, até parece bem simples, certo? As coisas começam a ficar complicadas quando precisamos cuidar de requisições assíncronas (geralmente chamadas de side effects em programação funcional).
Para cuidar desse problema, Redux sugere o uso de middlewares (mais específicamente o thunk middleware). A idéia básica é, se você precisa chamar algum side effect, você usa um action creator: uma função que retorna uma outra função que pode realizar requisições assíncronas e despachar qualquer ação que você queira.
Usando essa abordagem, rápidamente podemos ter action creators complexos e difíceis de testar. É aí que entra o redux-saga.
Ele usa o conceito de Sagas. Um modo declarativo e bem organizado de expressar side effects (como timeouts, chamadas de API, etc). Ao invés de você escrever um action creators usando o thunk middleware, nós continuamos despachando ações síncronas, mas, ao invés de um reducer cuidando dessas ações, nós teremos uma Saga recebendo essas ações e produzindo (yield) efeitos (no final são apenas simples objetos em JavaScript definindo as ações assíncronas).
Isso não é muito complexo? Action Creators parecem mais simples, não?
Na verdade…não! Mesmo que pareçam…nope, noop, nil!
Vamos escrever um action creators que busca alguns dados de uma API e despacha mudanças para nossa store.
function loadTodos() {
dispatch({ type: 'FETCHING_TODOS' });
fetch('/todos').then(todos => {
dispatch({ type: 'FETCHED_TODOS', payload: todos });
E para ser sincero, esse é um dos thunks mais simples que poderíamos escrever em Redux, e como você pode ver, o único modo para testar esse código, é usando algum tipo de mock para o método fetch.
Agora, vamos escrever essa função usando Sagas:
import { call, put } from 'redux-saga';function* loadTodos() {
yield put({ type: 'FETCHING_TODOS' });
const todos = yield call(fetch, '/todos');
yield put({ type: 'FETCHED_TODOS', payload: todos });
Como você pode ver, uma saga usa um ES6 Generator para produzir nossos side-effects (eu gosto de chamá-los de generator puro, ou pure generator, porque, na verdade, ele não irá executar o side effect, apenas, irá criar o objeto/função que descreve os efeitos a serem executados). No exemplo acima, nós temos dois tipos de efeitos:
o efeito put é um efeito que despacha uma ação para a nossa store
o efeito call é um efeito que irá chamar nossa requisição assíncrona (Promises, CPS ou outra saga).
Agora, para testar esse código, é bem simples, de verdade:
import { call, put } from 'redux-saga';const mySaga = loadTodos();
const myTodos = [{ message: 'text', done: false }];mySaga.next();expect(
mySaga.next().value
).toEqual(put({ type: 'FETCHING_TODOS' }));expect(
).toEqual(call(fetch, '/todos'));expect(
mySaga.next(myTodos).value)
.toEqual(put({ type: 'FETCHED_TODOS', payload: myTodos }));
Viu? Nenhum mock é necessário! Ciao, Adios, mocks!
Executando uma Saga
Um action creator é sempre executado quando despachamos a função criada pelo thunk middleware. Sagas são diferentes:
"…sagas são como rotinas daemons que são executadas em plano de fundo e escolhem sua própria lógica de progresso…" — Yassine Elouafi, criador do redux-saga
E para configurar o regux-saga em sua aplicação? Também é simples:
import sagaMiddleware from 'redux-saga';const createStoreWithSaga = applyMiddleware(
sagaMiddleware([loadTodos])
)(createStore);let store = createStoreWithSaga(reducer, initialState);
Combinando Sagas
Saga já é um efeito, assim com os reducers, podemos combina-las e criar composições de um modo bem simples (aliás, você pode ler meu artigo sobre ES6 Generators, eu recomendo um bom entendimento dessa nova maneira de escrever JavaScript).
No exemplo anterior, a saga loadTodos estava sendo executada no início da aplicação, mas e se quisermos executar uma saga a cada vez que um ação específica for despachada pela UI?. Nesse caso, seria algo como:
// loadTodos.js
import { fork, take } from 'redux-saga';// O mesmo "loadTodos" do exemplo acima
function* loadTodos() {
}function* watchTodos() {
while (yield take('FETCH_TODOS')) {
yield fork(loadTodos);
}...// store.js
// Iremos atualizar nossa configuração de sagas
// na criação da nossa store:
const createStoreWithSaga = applyMiddleware(
sagaMiddleware([watchTodos])
Aqui nós temos dois efeitos diferentes importados do redux-saga:
o efeito take irá escutar todas as redux actions despachadas para nossa store e só retornará true se a ação especificada (nesse caso, 'FETCH_TODOS') for despachada para a store.
o efeito fork irá criar um sub-efeito em plano de fundo, sem bloquear o main loop/UI da sua aplicação, enquanto executa a função passada como parâmetro.
Então, agora, vamos atualizar nossa lista de uma aplicação front-end usando uma arquitetura com Redux e Redux Saga, pode ser descrita como:
5. Talvez seja produzido algum tipo de side-effects em resposta as ações despachadas.
function* saga() { yield effect; }
6. Volte para o item 1.
Como você pode vez, redux-saga possibilita um modo declarativo e bem organizado de expressar seus side-effects.