Image for post
Image for post
Um modo simples e declarativo de cuidar dos seus side-effects!

Redux hoje em dia — De Action Creators até Sagas

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:

  1. Uma global store guardando o estado imutável da aplicação.
  2. 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() {
return dispatch => {
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(
mySaga.next().value
).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 { createStore, applyMiddleware } from 'redux';
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() {
yield put({ type: 'FETCHING_TODOS' });
const todos = yield call(fetch, '/todos');
yield put({ type: 'FETCHED_TODOS', payload: todos });
}
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])
)(createStore);

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.

Conclusão

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:

  1. Uma global store guardando o estado imutável da aplicação.
  2. 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. 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.

Existem outros efeitos, muito bem documentados por sinal, só dar uma olhada em:

Qualquer dúvida, só me mandar um tweet ou deixa um comentário por aqui!

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store