Estrutura Redux escalável com Ducks
O Redux é uma biblioteca de gerenciamento de estado baseada na arquitetura Flux utilizada principalmente no ecossistema React. Se você ainda não conhece o Redux, sugiro ler esse post antes de continuar.
Apesar do seu comportamento ser bem estruturado dentro de uma aplicação React, a estrutura de pastas e arquivos ainda é um obstáculo enfrentado por desenvolvedores. Nesse post trato sobre as alternativas estruturais mais utilizadas pela comunidade e a opção que deixou minha aplicação mais escalável até agora.
Separação por função
Geralmente quando começamos com essa lib criamos pastas para separar as actions, reducers e types, e dentro de cada diretório criamos arquivos que especificam cada módulo da aplicação. Vamos pensar em um app com autenticação, lista de amigos e posts, nesse caso teríamos três módulos gerenciados pelo Redux onde dividiríamos os arquivos da seguinte forma:
// store/actions/auth.js
// store/actions/friends.js
// store/actions/posts.js
// store/reducers/auth.js
// store/reducers/friends.js
// store/reducers/posts.js
// store/types/auth.js
// store/types/friends.js
// store/types/posts.js
O problema dessa abordagem é a repetição de arquivos com um mesmo nome e, além disso, a grande incidência de importações/exportações dentro desses arquivos.
Separação por módulo
Como forma de solucionar esse problema, em aplicações um pouco maiores, começou-se a abordar a separação de arquivos do Redux por módulos, resultando na seguinte estrutura:
// store/auth/actions.js
// store/auth/reducers.js
// store/auth/types.js
// store/friends/actions.js
// store/friends/reducers.js
// store/friends/types.js
// store/posts/actions.js
// store/posts/reducers.js
// store/posts/types.js
Dessa forma a estrutura já fica mais amigável e legível, porém ainda não temos algo que podemos chamar de escalável, isso porque a cada novo módulo precisamos criar três arquivos, realizar, assim como antes, vários imports e exports entre componentes e arquivos criados.
Duck Pattern
A partir dos problemas gerados pelas estruturas anteriores, uma nova forma de organização dos arquivos foi desenvolvida a fim de diminuir o tanto de arquivos gerados por um novo módulo na aplicação, essa estrutura foi nomeada como Ducks Modular Redux.
A principal diferença entre os modelos anteriores é a união de actions, reducers e types em um único arquivo. “Mas esse arquivo não vai ficar grande demais?”, na maioria dos casos não, isso porque no Redux dificilmente um módulo irá conter mais do que 5–6 actions. Vejamos como a estrutura ficaria nesse caso:
// store/ducks/auth.js
// store/ducks/friends.js
// store/ducks/posts.js
Dessa forma não teremos três arquivos para cada módulo ou função diminuindo a quantidade de importações/exportações entre eles. Vamos entender agora como o arquivo Duck é composto (fiz algumas alterações comparado com a documentação original que fizeram mais sentido pra mim):
// store/ducks/auth.js
// Action Types
export const Types = {
LOGIN: 'auth/LOGIN',
LOGOUT: 'auth/LOGOUT',
};
// Reducer
const initialState = {
isLogged: false,
token: null,
user: {},
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case Types.LOGIN:
return {...};
case Types.LOGOUT:
return {...};
default:
return state;
}
}
// Action Creators
export function login(username, password) {
return {
type: Types.LOGIN,
payload: {
username,
password
},
}
}
export function logout() {
return {
type: Types.LOGOUT,
}
}
Veja que tenho meus Action Types, Action Creators e Reducer unificados em um arquivo. Dessa forma na hora de importar o reducer desse arquivo para repassar ao store posso apenas realizar um import default:
import auth from 'store/ducks/auth';
const reducers = combineReducers({
auth,
});
Para importar os Action Types para utilizar em middlewares ou qualquer outro arquivo podemos realizar a importação dessa forma:
import { Types } from 'store/ducks/auth';
E para utilizar Action Types de dois módulos em um mesmo arquivo basta renomeá-los:
import { Types as AuthTypes } from 'store/ducks/auth';
Para importar os Action Creators nos componentes basta importarmos cada um que vamos utilizar:
import { login, logout } from 'store/ducks/auth';
class Componente extends Component {...}
const mapDispathToProps = dispatch =>
bindActionCreators({ login, logout }, dispatch);
export default connect(null, mapDispatchToProps)(Componente);
E dessa forma temos uma estrutura mais escalável para trabalhar com o Redux unificando os arquivos de cada módulo em um “duck”.
Concluindo
Eu construí muitas aplicações React/React Native até hoje e até agora não encontrei uma estrutura melhor que a de “ducks”. Isso não quer dizer que pra você ela vai funcionar melhor, fique livre pra escolher o que fizer sentido pro seu projeto.
Não existe uma estrutura perfeita para todo tipo de projeto, isso é impossível. Em projetos de pequena escala, talvez utilizar “ducks” seja atirar no próprio pé e criar apenas um arquivo de Actions e Reducers faça mais sentido. Eu defendo que a organização pode ser mantida independente de estrutura e que cabe ao desenvolvedor selecionar a melhor para cada ocasião.
Ainda assim, pensando em escalabilidade do projeto recomendo estudar o Duck Pattern e se possível adicionar o Redux Sauce que irá aumentar a produtividade do seu fluxo ainda mais.
ps.: vou escrever um post sobre o Redux Sauce em breve, então fica ligado aqui no Blog ?