Por se tratar de uma biblioteca, o React é agnóstico e permite que existam diversos caminhos felizes para sua aplicação, sendo assim é comum a existência de dependências externas para controle do estado global integrados ao seu ecossistema.
Durante a concepção do React não havia uma receita de bolo para controlar o estado global de forma integrada ao sistema e muito menos recurso disponível ou implementado. O caminho sugerido para aplicações em grande escala era o Flux que foi criado pelo próprio Facebook, não era só para controle de estado global e sim para atender aplicações de grande porte. Sendo assim faz sentido usar o Flux para qualquer aplicação? Não tentem matar uma formiga com uma bazuca.
Durante a evolução do React surgiram bibliotecas com motivações de padronizar a comunicação entre os componentes e o contexto externo, seja uma requisição ou um banco local. Existem duas bibliotecas que trabalham para resolver um problema em comum, porém com abordagens diferentes o Redux e MobX.
Redux
Criado em 2015 por Dan Abramov e Andrew Clark seu próprio nome o traduz o funcionamento que é baseado em funções reduce para manter o estado global. Apesar de inspirado no Flux, traz a simplicidade unificando tudo em um único store, o Redux pode facilitar o desenvolvimento, teste e debug das aplicações, já que toda mudança de estado global é disparada por uma action que por sua vez invoca reducers impactando na mudança do estado.
Apesar de bem documentado e robusto, o Redux, não é bala de prata, o próprio criador do Redux escreveu um artigo sugerindo que comece o projeto com React para entender se as necessidades do projeto podem ser resolvidas pelo Redux.
"Instead learn to think in React. Come back to Redux if you find a real need for it, or if you want to try something new. But approach it with caution, just like you do with any highly opinionated tool."
Referências: You Might Not Need Redux - Dan Abramov
Prós
- Convenções que levam a adoção de padrões;
- Estado imutável, imutabilidade é um conceito difundido em programação funcional, cada ação resulta em um novo estado. O Redux mantém uma cópia de cada estado isolando de qualquer hack que você queira fazer;
- Time Travel, como cada ação reflete em um novo estado é possível viajar no tempo, como desfazer ações e até mesmo refazer;
- Suporte, Dan Abramov trabalha no Facebook sendo assim podemos garantir que haverá suporte parcial da equipe React;
- Dev Tools, há diversas extensões que permitem monitorar e depurar mudanças de estado;
Contras
- Verboso - Não é muito difícil tornar o projeto verborrágico já que estamos falando de actions, reducers, states, selectors e outros milhões de helpers e pacotes que seu projeto provavelmente tem dependência;
- Boilerplate, muitas vezes a falta de um pode induzir ao erro;
- Curva de aprendizagem, não é tão intuitivo para desenvolvedores que vieram da orientação a objeto, entretanto para quem trabalha com funcional é bem claro;
MobX
Patrocinado por empresas conceituadas como Algolia, Coinbase e Facebook Open Source, o MobX é uma alternativa para o controle do estado global utilizando processos reativos. Assim como o Redux não é específico para React, é compatível com outras bibliotecas ou frameworks como Angular e Vue.
É divido em quatro partes:
- Observable state, qualquer tipo de valor pode ser mutável seja primitivo ou complexo;
- Computed values, valores computáveis podem ser concatenação de string ou seletores;
- Reactions, semelhante a um valor computado, em vez de produzir um novo valor, produz um efeito colateral. Este efeito pode ser a mudança de dados que pode resultar em alguma mudança no DOM;
- Actions, são os principais meios para modificar o estado;
Prós
- Curva de aprendizagem, os Stores do Mobx são reativos ou seja você pode manipular diversos tipos de objetos e o MobX irá cuidar da renderização por causa dos Observables;
- Menos verboso, já que você não escreve Actions + Reducers no padrão Redux, há um ganho neste quesito;
- Sem middleware, por trabalhar de outra forma não há a necessidade de utilizar middleware como o Thunk para ações assíncronas;
- Store flexível, além de manter dados o Store pode ser qualquer coisa desde uma classe proxy que depende de uma API;
- Dev Tools, assim como o Redux há extensões para Chrome e Firefox, ótimo para acompanhar as mudanças de estado;
Contras
- Interpretação, por ser muito simples podemos encontrar equívocos de implementação;
- Renderização, como há reatividade podem ocorrer renderizações em massa impactando na performance, sendo assim use o Observable com moderação.
Show me the code
Apenas para comparação e experiência criei duas implementações Redux e Mobx.
O projeto é um TODO-LIST utilizando o Parcel.js como bundler para facilitar e simplificar o trabalho.
Serão exibidas partes principais da implementação, o código completo pode ser visualizado no GitHub.
import { combineReducers } from 'redux'
import { ADD_TASK, CLEAR_TASKS, TOGGLE_TASK, FILTER_TASKS } from './../actions/todoList'
const initialState = {
tasks: [],
filters: {
status: null
}
}
const todoList = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK:
return {
...state,
tasks: [ ...state.tasks, action.task ]
}
default:
return state
}
}
const todoListReducer = combineReducers({
todoList
})
export default todoListReducer
Redux
Actions
export const ADD_TASK = 'ADD_TASK'
export const addTask = task => {
return {
type: ADD_TASK,
task
}
}
Reducers
Components
//app.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { ToDoListComponent } from './todoList'
import store from './store'
render(
<Provider store={store}>
<ToDoListComponent />
</Provider>,
document.getElementById('root')
)
//main.jsx
import React from 'react'
import { connect } from 'react-redux'
import { addTask, toggleTask, clearTasks, filterTasks } from '../actions/todoList'
import { getTasks } from '../selectors'
const Home = () => {
const handleClear = () => clearTasks()
// ...
return (
<section>
</section>
)
}
const mapStateToProps = (state, _ownProps) => ({
tasks: getTasks(state)
})
const mapDispatchToProps = (dispatch, _ownProps) => ({
addTask: task => dispatch(addTask(task))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Home)
Mobx
Store
import { action, observable, computed } from 'mobx'
class TasksStore {
@observable data = []
@observable filter = null
@action setFilter = filter => { this.filter = filter }
@action addTask (task) {
this.data.push(task)
}
getTasks (filter) {
return this.filter === null
? this.data
: this.data.filter(x => x.done === filter)
}
@computed get tasks () {
return this.getTasks(this.filter)
}
}
export default TasksStore
Component
//app.jsx
import React from 'react'
import { render } from 'react-dom'
import { configure } from 'mobx'
import { ToDoListComponent } from './todoList'
import { appStore } from './store'
import DevTools from 'mobx-react-devtools'
configure({ enforceActions: 'always' })
render(
<div>
<DevTools />
<ToDoListComponent store={appStore} />,
</div>,
document.getElementById('root')
)
//main.jsx
import React from 'react'
import ToDoListInput from './input'
import ToDoListTasks from './tasks'
import ToDoListFooter from './footer'
const Home = props => {
const handleClear = () => props.store.tasks.clearTasks()
// ...
return (
<section>
</section>
)
}
export default Home
Rodando
Aqui estão as implementações publicadas no Surge, não é só mostrar o código, quero ver funcionar rs.
Conclusão
Neste exemplo podemos encontrar as convenções do Redux e uma lado verboso comparado ao MobX. Fica bem claro que cada biblioteca trabalha de maneira diferente, resumidamente o MobX apoia a reatividade e o Redux abraça a programação funcional utilizando fortemente a imutabilidade.
Na concepção de um projeto React, se há uma nível de experiência da equipe em projetos com Vue ou Angular, penso que o MobX se encaixa melhor na minha opinião.
As convenções Redux ajudam os desenvolvedores a não reinventar a roda, é muito comum encontrar a combinação React + Redux, já que há uma forte comunidade, entretanto é possível encontrar projetos onde Redux não deveria estar, por este motivo peço que comparem qual abordagem facilitaria manutenção e tornaria o código limpo.