Entendendo Flux
No post anterior, dei uma breve introdução sobre React.js, mas chega a hora de deixar a brincadeira mais interessante ;)
Vamos falar sobre Flux, um padrão de arquitetura criado/inventado pelo Facebook para lidar com um problema na construção de aplicações front-end baseadas em componentes. Sim, isso é meio confuso (e muito) a primeira vista, mas vamos a algumas explicações.
Você pode ler toda a parte técnica a seguir(aka blablabla), ou ir direto para a parte do código.
O que é?
Atualmente está surgindo um novo paradigma no mundo de desenvolvimento de web apps. Esses aplicativos são quebrados em componentes reutilizáveis e combináveis/agregáveis e como consequência desse conceito é que os aplicativos seguem hierarquias baseadas em componentes. A grande vantagem de se utilizar componentes é que eles são desacoplados e isolados, o que não só facilita a manutenção, mas também pode impactar positivamente na sua produtividade ou a do seu time.
Para aqueles que estão acostumados com o data-binding convencional como eu, vão sem dúvidas ter “problemas” com o fluxo de dados nesse padrão de arquitetura.
Quando os desenvolvedores do Faceboook começaram a utilizar React dentro de sua plataforma, provavelmente sofreram com esses mesmos problemas e acabaram por definir esse novo padrão de arquitetura utilizando um método de transporte de dados unidirecional.
Porem, Flux esta mais para um novo conjunto de padrões do que um framework, e você pode utilizar em seus projetos sem ter que escrever/reescrever muito código.
Conceitos arquitetônicos
Não vamos pensar em React por hora, porque agora que começa a parte legal, já que Flux é um pattern que não deve ser confundido com MVC, pois os conceitos que são necessários para entender o funcionamento das coisas é um paradigma diferente do que estamos acostumados:
Actions
São os métodos para auxiliar o envio de informações ao dispatcher.
Dispatcher
É o ponto central da arquitetura, responsável por gerenciar/transmitir todo o fluxo de dados para todos os que devem receber aquela informação.
O Dispatcher é um Singleton, porem foi baseado no padrão Publish Subscribe e é um ponto de registro de callbacks para as stores. Cada store se registra e fornece um callback, e quando o dispatcher responder a uma ação, todas as stores registradas recebem os dados fornecidos pela ação.
Utilizamos apenas um dispatcher na aplicação justamente pelo fato dele ser o pilar da aplicação, logo sua aplicação deve ter apenas um único dispatcher.
Stores
É o lugar onde fica armazenada toda a lógica e o estado de sua aplicação e a implementação dos callbacks registrados para o dispatcher.
Stores tem o papel pouco semelhante a um model no MVC tradicional, mas além disso elas gerenciam o estado de muitos objetos e não instâncias de um único objeto.
Views
São os componentes em React que trazem os estados das stores e passam para os componentes filhos através de suas “props”, onde temos um ou mais componentes root que escutam eventos de suas próprias stores. Podemos chamar isso de uma view-controller ainda que um pouco diferente de controller no modelo MVC, já que uma vez que obtem os dados das stores, atualiza seus componentes dependentes seguindo a estrutura top-down, podendo assim controlar qualquer parte significativa da página.
Flux !== MVC
Como disse anteriormente, Flux não deve ser confundido com MVC, pois suas actions são acionadas diretamente de suas respectivas stores através do dispatcher. Mas para isso não ficar uma completa bagunça, imagine que stores são proximas a camada model, mas é muito complicado comparar a camada view pois ela é também o próprio controller de uma aplicação Flux.
Fluxo
Já que ele não segue o modelo MVC, o flow também é diferente por ser unidirecional seguindo:
Actions -> Dispatcher -> Stores -> React Views.
Sempre será esse o fluxo da sua aplicação, caso um componente React (View-Controller) realizar alguma ação, uma nova action será executada enviando informações ao dispatcher que irá atualizar as stores registradas que atualizará todas as views que precisarem ser afetadas.
Estruturando nossa aplicação
Nossa aplicação será um simples Shopping Cart, onde temos duas principais áreas, sendo a vitrine de produtos, e nosso carrinho em sí.
Você pode dar uma olhada neste Tutorial oficial do Facebook fazendo um Todo List.
Como eu acho que tenho bom gosto, nossa loja irá vender cervejas, porque apreciar cervejas é vida <3
Feito, vamos atualizar nosso package.json
com os seguintes modulos:
- Browserify
- Reactify
- React
- Flux
HTML
Sim, utilizei o Bootstrap porque não estava com saco pra fazer algo do zero, e não estou nem ai se as classes dele não fazem o menor sentido.
Mas por favor, adicione também este arquivo CSS. Sua loja vai ficar mais cool. É sério, adiciona!
Mock
Como não temos um servidor para solicitar os produtos com chamadas AJAX, vamos criar objeto mock de retorno com alguns produtos.
Ridiculo não?
Pense que em /utils você ira jogar toda e qualquer parte do seu sistema que não faça parte da estrutura flux.
Claro que você pode utilizar outros nomes, mas normalmente utils e helpers são mais genéricos as vezes até de mais.
Actions & Constants
Actions são coleções de métodos que são chamadas pelas nossas “views” que enviam ações para o dispatcher contendo payloads que serão entregues as stores.
Um ponto interessante é que o Facebook as usa em conjunto com as constantes da aplicação, passando via payload o actionType
usando assim as constants.
De uma olhada no arquivo CartConstants.js
:
Uma outra abordagem para criar nossas constants é utilizando a lib keyMirror, que simplesmente duplica o valor da chave para uma string, sem a necessidade de escrever novamente.
Eu não acho necessário, mas pode ser interessante para a sua aplicação.
Agora nosso arquivo CartActions.js
:
Perceba que nossas actions são bem simples. Nós carregamos os produtos, adicionamos e removemos produtos dos carrinho e manipulamos a visibilidade do carrinho.
Utilizamos o método handleViewAction
que iremos implementar em nosso dispatcher que recebe um objeto como parametro onde por default adicionamos a propriedade actionType
que utiliza nossas constants como recurso.
O Dispatcher
Falamos muito sobre o Dispatcher, mas vamos ver na prática como utilizar esse cara.
O Dispatcher é o cara que gerencia basicamente todo o processo da nossa aplicação, e a grande mágica que o faz ser o ponto central são os métodos register
e dispatch
, que são nossos triggers de eventos a entre a ação que disparou o evento e as stores registradas. Ele simplesmente recebe a action e propaga para as stores que ira identifica-la e disparar um evento caso registrado.
Meh… então é um Pub/Sub mesmo?
Mais ou menos isso, já que conceito do dispatcher é transmitir payloads para todas as stores registradas e seus respectivos callbacks.
Nosso arquivo AppDispatcher.js
vai ficar assim:
Note que criei uma instancia do Dispatcher e também o método handleViewAction
com a simples finalidade de abstrair entre nossas actions das Views e nossas actions vindas de uma API/Servidor.
O método utiliza o método dispatch
que faz toda a mágica de emitir o evento contendo em nossa propriedade action
o payload com a ação/action e nosso dados.
Gotchas / Tricks
Sua aplicação pode depender da execução/atualização de alguma outra store antes de renderizar novamente um componente, etc. Isso é possível utilizando o método waitFor
que me foi muito útil quando passei por isso.
Exemplo:
Quando o dispatcher transmitir a action PURSHASE_ITEMS
no CartStore
, antes de executar o método checkout
, ele deve aguardar a execução dos eventos em ProductsStore
.
Stores
Falamos muito sobre stores, e agora que temos nossas actions definidas, chegou a hora de criar nossas stores.
Cada Store gerencia o estado de um dominio especifico da aplicação, nesse caso vamos criar nossa primeira store que sera responsável por gerenciar os produtos da loja, a ProductStore.js
.
Utilizei a notação class
do ES6 apenas para facilitar a criação da classe ProductStoreFactory
, que por sua vez estende os métodos do EventEmitter
onde adicionamos os métodos emitChange
, addChangeListener
e removeChangeListener
.
Definimos os métodos o método privado loadProducts
fora da nossa classe que adiciona a lista de produtos em nosso Array _products
. Também deixamos o método getProduct
exposto que nos retorna a lista de produtos.
Por fim, adicionamos um callback em nosso AppDispatcher
através do método register que determina se o payload transmitido bate com alguma combinação em nosso bloco switch
e então emitimos uma mudança em ProductStore
.
O próximo passo é criar nosso CartStore
:
Basicamente o mesmo que a store anterior, temos nossa lista de produtos armazenada em _products
que por sua vez é um objeto para facilitar a localização dos produtos através de sua ‘key’ e _cartVisible
para definir o status do nosso cart.
Também adicionamos alguns métodos publicos que serão utilizados pelo View-Controller para definir o state da aplicação:
getCartItems
: Retorna os itens em nosso carrinho;getCartAmount
: Retorna o total de itens em nosso carrinho;getCartTotal
: Retorna o valor total dos itens em nosso carrinho;getCartVisible
: Retorna um boolean que irá omitir ou mostrar o carrinho em nosso layout.
Você deve ter percebido que nessa store temos alguns métodos que são privados como _addItem
, _removeItem
e _setVisibility
que serão utilizados a cada vez que um action vindo do payload bater com a condição em nosso register.
O legal é que em _addItem
, ao invés de simplesmente jogar o produto dentro do carrinho, eu preferi fazer um mapper com a função ProductCartModel poderia ter usando ES6, mas… para me retornar um objeto apenas com o que realmente é necessário para o meu carrinho.
A cada vez que um produto é adicionado, nosso método verifica se o mesmo id encontra-se em nossa lista de produtos; Se sim, simplesmente incrementamos a quantidade; Do contrário criamos um novo produto mapeado e inserimos em nossa lista.
Agora que temos nossas stores criadas, vamos criar nossos Views e os respectivos componentes.
View Controller
Apenas recaptulando que “view-controller” não tem nada haver com o padrão MVC, são apenas componentes que irão escutar por mudanças baseadas no *estado da aplicação vindas de nossas stores.
Vamos começar pelo simples, o app.js
.
Agora que já temos um norte para seguir, nesse caso o componente Application.js
, responsável por ser o root da nossa aplicação.
Vamos dar uma olhada:
Sim, esse cara faz muitas coisas coisa pra caralho…, carrega nossas stores, adiciona os listeners nos eventos componentDidMount
e componentWillUnmount
no lifecycle do componente, renderiza os componentes CartComponent
e ProductsComponent
e ainda seta um estado/state inicial do componente que ira ser passado como referência para os demais componentes filhos através do método getInitialState
.
Vejamos… cada componente, esta sendo renderizado através de uma função, isso eu descobri sendo uma boa prática ao invés de renderizar todos os N componentes direto no método render
e depois simplesmente chamar executando o mesmo como por exemplo {this.renderCartComponent()}
.
Agora vamos ao componente de produtos:
O código acima esta bem auto-explicativo, mas vou dar alguns highlights:
Ao carregar o componente, o método _loadProducts
é executado, o qual irá carregar os nossos produtos de algum lugar (nesse caso nosso ProductsMock). Utilizei setTimeout
apenas para garantir um processo assíncrono;
Renderizamos o componente <Product />
para cada produto que tivermos em nossa store utilizando Array.map() passando uma key para que cada componente seja único, o produto em sí e o método addProduct que irá fazer o trigger addToCart
em nossas actions definidas anteriormente.
Antes de passarmos ao carrinho, vamos dar uma olhada em como ficou nosso <Product />
:
Esse sem dúvidas é o componente mais simples da aplicação, quase um componente burro, já que o único evento que ele tem é nosso click, que irá disparar o método addProduct
passado como propriedade.
Enfim, vamos ao <CartComponent />
:
Também bem auto-explicativo, com pequenas diferença devido a função exercida, mas nada de mais:
- toggleCartDisplay é o responsável por mostrar/esconder o carrinho em sí;
- removeProduct faz o trigger
removeFromCart
em nossas actions; - renderProductsList renderiza a lista de produtos dentro do carrinho, fazendo essa gambiarra que você esta vendo, já que estamos utilizando um objeto como lista.
E é isso, só rodar e ser feliz. LoL
Conclusão
Eu deveria ter escrito este post logo na sequência, mas tive alguns imprevistos e acabei deixando-o de lado por algumas semanas.
Entenda que React + Flux é sem sombra de dúvidas uma combinação poderosa, mas você deve ter em mente a necessidade de utiliza-la e como aplica-la em seu projeto, se não tudo pode vira uma completa salada, nada fica claro e alguem vai ter pesadelos sempre que for manter a aplicação.
Atualmente venho fazendo coisas legais com React, inclusive peguei alguns freelas para o exterior, onde dois projetos estavam utilizando essa mesma arquitetura com outros patterns como o Reflux, que tem uma leve mudança quando ao Flux, mas nada muito complexo.
Você pode baixar este exemplo aqui.
Join the Conversation