flux fundamentos javascript react

Calculadora em javascript com React e Flux – Parte 3

Compartilhe os ensinamentos do mestre

Recapitulando

Finalmente chegamos a parte final do nosso mini curso de React com Flux. Se você ainda não conferiu as outras duas partes então comece por aqui:

Tutorial react e flux – parte 1

Tutorial react e flux – parte 2

No post anterior nós criamos nosso Store e registramos o visor da calculadora para receber as mudanças de estado dele. Agora é hora de ver o que é o Dispatcher e as Actions para finalizarmos nossa calculadora.

Hora de criar o dispatcher

A arquitetura flux tem 4 componentes básicos: as views, os stores, os dipatchers e as actions(ou action creators). Já vimos o papel das views e dos stores, agora vamos entender os dispatchers.

O dispatcher será o responsável por receber os dados das actions e enviar esses dados para todos os stores. Isso mesmo independente de que action tenha disparado a chamada todos os stores serão notificados. É resposabilidade de cadas store decidir se a action passada para ele é util ou não, em um minuto já vou mostrar como isso funciona.

Vamos criar o dispatcher

Primeiramente instale o flux no seu projeto, para fazer isso abra o terminal (ou prompt de comando), navegue até o diretório do seu projeto e execute este comando aqui:

npm install –save flux

Dentro do seu projeto no diretório src/flux/ crie um arquivo chamado AppDispatcher.js e dentro dele adicione o seguinte código:

import {Dispatcher} from 'flux'

class AppDispatcher extends Dispatcher{
    
}

export let appDispatcher = new AppDispatcher();

Bem simples né? Tudo que fizemos aqui foi estender o Dispatcher padrão do flux e exportar uma única instância dele na última linha. Assim toda vez que o appDispatcher for importado por outras classes, a mesma instância sempre será retornada e não haverá problemas.

O próximo passo é registrar nosso Store para receber eventos do app Dispatcher.

Recebendo eventos no Store atráves do Dispatcher

Antes de alterar nosso store, vamos criar um arquivo para listar todos os possíveis nomes das actions que podem ser recebidas pelo nosso store como constantes. Isso vai ajudar a manter o código mais organizado, pois sempre será possível referenciar o nome da ação como uma constante. Assim não teremos risco de nos confundirmos e digitar o nome da action errado.

No diretório src/flux/ crie um arquivo chamado Constants.js e coloque o seguinte código dentro dele:

export default {
    ATUALIZA_VISOR: 'ATUALIZA_VISOR',
    GUARDA_VALOR:'GUARDA_VALOR',
    LIMPA_VISOR:'LIMPA_VISOR',
    LIMPA_VISOR_NA_PROXIMA_OPERACAO:'LIMPA_VISOR_NA_PROXIMA_OPERACAO',
    SETA_OPERACAO:'SETA_OPERACAO',
    LIMPA_OPERACAO:'LIMPA_OPERACAO',
    ENTRAR_COM_DIGITOS_DECIMAIS:'ENTRAR_COM_DIGITOS_DECIMAIS'
}

pronto agora que já definimos todos os nomes dos eventos que interessam ao nosso store, precisamos escrever o código que vai de fato tratar esses eventos.

Vamos voltar ao nosso store e criar um método para tratar os eventos que serão enviados pelo dispatcher. Altere seu arquivo CalculadoreStore.js para que ele fique assim:

import EventEmmiter from 'events'
import Action from './Constants'
import {appDispatcher} from './AppDispatcher'
const CHANGE = 'change'

let _store = {
    valorDoDisplay:'0',
    resultadoUltimaOperacao:0,
    limparVisor:false,
    operacaoAritmetica: undefined,
    modoDeEntradaDecimal: false,
}

class CalculadoraStore extends  EventEmmiter{

    getValorDoDisplay(){
         return _store.valorDoDisplay;
    }

    setValorDoDisplay(valor){
        _store.valorDoDisplay = valor;
        this.emit(CHANGE);
    }

    getResultadoUltimaOperacao(){
        return _store.resultadoUltimaOperacao;
    }

    setResultadoUltimaOperacao(valor){
        _store.resultadoUltimaOperacao = valor;
        this.emit(CHANGE);
    }

    getLimparVisor(){
        return _store.limparVisor;
    }

    setLimparVisor(valor){
        _store.limparVisor = valor;
        this.emit(CHANGE);
    }

    getOperacaoAritmetica() {
        return _store.limparVisor;
    }

    setOperacaoAritmetica(valor){
        _store.operacaoAritmetica = valor;
        this.emit(CHANGE);
    }

    getModoDeEntradaDecimal(){
        return _store.modoDeEntradaDecimal;
    }

    setModoDeEntradaDecimal(valor){
        return _store.modoDeEntradaDecimal = valor;
        this.emit(CHANGE);
    }

    getState(){
        return _store
    }

    handleActions(action){
        switch(action.type){
            case Action.ATUALIZA_VISOR:
                this.setValorDoDisplay(action.value);
                break;
            case Action.ENTRAR_COM_DIGITOS_DECIMAIS:
                this.setModoDeEntradaDecimal(action.value);
                break;
            case Action.GUARDA_VALOR:
                this.setResultadoUltimaOperacao(action.value);
                break;
            case Action.LIMPA_OPERACAO:
                this.setOperacaoAritmetica(undefined);
                break;
            case Action.LIMPA_VISOR:
                this.setValorDoDisplay('')
                break;
            case Action.LIMPA_VISOR_NA_PROXIMA_OPERACAO:
                this.setLimparVisor(action.value);
                break;
            case Action.SETA_OPERACAO:
                this.setOperacaoAritmetica(action.value);
                break;
        }
    }
}

export let calculadoraStore = new CalculadoraStore()
appDispatcher.register(calculadoraStore.handleActions.bind(calculadoraStore))

 

As mudanças mais importantes no nosso arquivo são as seguintes:

handleActions(action){
        switch(action.type){
            case Action.ATUALIZA_VISOR:
                this.setValorDoDisplay(action.value);
                break;
            case Action.ENTRAR_COM_DIGITOS_DECIMAIS:
                this.setModoDeEntradaDecimal(action.value);
                break;
            case Action.GUARDA_VALOR:
                this.setResultadoUltimaOperacao(action.value);
                break;
            case Action.LIMPA_OPERACAO:
                this.setOperacaoAritmetica(undefined);
                break;
            case Action.LIMPA_VISOR:
                this.setValorDoDisplay('')
                break;
            case Action.LIMPA_VISOR_NA_PROXIMA_OPERACAO:
                this.setLimparVisor(action.value);
                break;
            case Action.SETA_OPERACAO:
                this.setOperacaoAritmetica(action.value);
                break;
        }
    }

Criamos essa função que vai receber as actions e de acordo com o tipo de ação recebida vai invocar um método diferente.

Note que em cada case o padrão é sempre o mesmo, escolhemos a função que iremos chamar no nosso CalculadoraStore de acordo com o valor de action.type e em seguida invocamos o método correspondente passando action.value.

Agora você deve estar se perguntando “mas de onde surge essa action? E quem vai invocar esse método?”. Esse método será invocado no nosso store pelo appDispatcher e é por isso que temos que registrar o método handleAction nele. Fazemos isso neste trecho de código aqui:

appDispatcher.register(calculadoraStore.handleActions.bind(calculadoraStore))

Adicionando a lógica da calculadora através das Actions

Bom agora que já vimos as View, os Stores e o Dispatcher. Precisamos entender o papel das actions.

As actions serão responsáveis por cuidar da maior parte da lógica da nossa aplicação e as views poderão invocar actions para desencadear as mudanças na calculadora. Ou seja, é a action que vai chamar o dispatcher e disparar os eventos, por sua vez o dispatcher vai mandar os eventos para todos os Stores e estes irão decidir se o evento interessa ou não e em caso de alteração de estado irão notificar as views que serão renderizadas novamente de acordo com o novos valores. Assim o ciclo inteiro fica completo (View –> Action –> Dispatcher –>Store –> View).

Vamos criar nossas Actions.

em src/flux/ crie um arquivo chamado CalculadoraActions.js e insira o seguinte conteúdo nele.

import {appDispatcher} from './AppDispatcher'
import Action from './Constants'



export function atualizarVisor(valorAntigo,valorPressionado,limparDisplayNaProximaOperacao,modoDeEntradaDecimal){
    //por padrao colocamos o valor pressionado para ser o novo valor
    let novoValor = valorPressionado;
    //se nao vamos limpar o visor entao precisamos modificar valor present
    if(!limparDisplayNaProximaOperacao){
        valorAntigo = parseFloat(valorAntigo);
        valorPressionado = parseFloat(valorPressionado);
        //se o modo de entrada nao eh decimal multiplicamos o valor por 10 e somamos o novo
        //valor para criar o novo valor
        if(!modoDeEntradaDecimal){
            novoValor = valorAntigo*10+valorPressionado;
        }
        else{
            //em caso de modo decimil esse codigo coloca as casas depois da virgula
            novoValor = valorAntigo + valorPressionado/Math.pow(10,quantidadeCasasdecimais(valorAntigo)+1)
        }
    }
    else{
        //se entramos aqui eh porque vamos limpar o visor e colocar um novo valor
        //nesse caso setamos setLimparVisorNaProximaOperacao para falso
        //pois o visor jah foi limpo
        setLimparVisorNaProximaOperacao(false)
    }
    //finalmente despachamamos a acao para os stores via appDispatcher
    appDispatcher.dispatch({type:Action.ATUALIZA_VISOR,value:novoValor})
}

export function executarOperacaoMatematica(operacaoAtual,operacaoNaMemoria,valorNoVisor,valorNaMemoria){
    debugger
    let resultado = valorNoVisor;
    //lemos os valores e convertemos eles para float
    valorNoVisor = parseFloat(valorNoVisor)
    valorNaMemoria = parseFloat(valorNaMemoria)
    appDispatcher.dispatch({type:Action.LIMPA_OPERACAO})
    setModoDeEntradaDecimal(false);

    //se nao escolhemos = como operacao, precisamos guardar a operacao
    if(operacaoAtual!== '='){
        appDispatcher.dispatch({type:Action.SETA_OPERACAO,value:operacaoAtual})
    }

    //se jah temos uma operacao na memoria isso significa q eh hora
    //de executar ela
    if(operacaoNaMemoria){
        switch(operacaoNaMemoria){
            case '+':
                resultado = valorNoVisor+valorNaMemoria;
                break;
            case '-':
                resultado = valorNoVisor-valorNaMemoria;
                break;
            case '*':
                resultado = valorNoVisor*valorNaMemoria;
                break;
            case '/':
                resultado = valorNoVisor/valorNaMemoria;
                break;
        }
    }

    //finalmente despachamos o novo valor
    appDispatcher.dispatch({type:Action.GUARDA_VALOR,value:resultado});
    piscar(resultado)
    setLimparVisorNaProximaOperacao(true)

}

export function setLimparVisorNaProximaOperacao(value){
    appDispatcher.dispatch({type:Action.LIMPA_VISOR_NA_PROXIMA_OPERACAO,value:value})
}

export function setModoDeEntradaDecimal(value){
    appDispatcher.dispatch({type:Action.ENTRAR_COM_DIGITOS_DECIMAIS,value:value})
}

export function setVisor(value){
    appDispatcher.dispatch({type:Action.ATUALIZA_VISOR,value:value})
}

export function resetar(){
    appDispatcher.dispatch({type:Action.GUARDA_VALOR,value:0});
    appDispatcher.dispatch({type:Action.LIMPA_OPERACAO});
    piscar(0)
}

export function piscar(value){
    setVisor('')
    setTimeout(() =>{
        setVisor(value)
    },100)
}

function quantidadeCasasdecimais(value) {
    if(Math.floor(value) === value) return 0;
    return value.toString().split(".")[1].length || 0;
}

Repare que a lógica do funcionamento está contido nesse arquivo.

Outro ponto importante é que sempre que precisamos enviar dados ao store fazemos isso através do Dispatcher, como por exemplo nessa linha:

//finalmente despachamos o novo valor
    appDispatcher.dispatch({type:Action.GUARDA_VALOR,value:resultado});

Sempre passamos o tipo de ação e o valor invocando o método dispatch. Todos os Stores vão receber essa ação e irão decidir se fazem algo com ela ou não. No nosso caso só temos um Store, mas caso tivessemos outros Stores eles também receberiam esses valores e poderiam descarta-los caso não precisassem deles.

Agora a única coisa que precisamos fazer é chamar nossas actions nas views.

Atualizando as views para chamarem as actions

Precisamos fazer duas mudanças para que nossas views possam chamar as actions.

A primeira é passar os valores que iremos passar às actions via props para os componentes filhos, pois os botões da calculadora vão precisar deles para passarem para as actions

Atualize o arquivo Calculadora.js com o seguinte conteúdo:

import React from 'react'
import Visor from './Visor'
import Teclado from './Teclado'

import {calculadoraStore} from './../flux/CalculadoraStore'

export default class Calculadora extends React.Component{

    constructor(props){
        super(props)
        this.state = calculadoraStore.getState()
    }

    componentWillMount(){
        calculadoraStore.on('change', ()=>{
            this.setState(calculadoraStore.getState())
        })
    }

    render(){
        return (
            <div id="calculadora">
                <Visor valor={this.state.valorDoDisplay}/>
                <Teclado valorNaMemoria={this.state.resultadoUltimaOperacao} operacaoAnterior={this.state.operacaoAritmetica} valorVisor={this.state.valorDoDisplay} limparNaProximaOperacao={this.state.limparVisor}
                         entradaDecimal={this.state.modoDeEntradaDecimal} />
            </div>
        )
    }
}

Simplesmente estamos passando os valores do estado via props. Essa foi a única mudança que fizemos em relação ao último post.

Agora precisamos mudar o Teclado para passar os valores para os botões.

Atualize o arquivo Teclado.js com o seguinte conteúdo:

import React from 'react'
import Botao from './Botao'

export default class Teclado extends React.Component{
    constructor(props){
        super(props)
    }

    render(){
        return(
            <div id="teclado">
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="c"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="+/-"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="%"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="/"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="7"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal}valor="8"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="9"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="*"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="4"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="5"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="6"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="+"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="1"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="2"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="3"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="-"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="0"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="."/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="pi"/>
                <Botao valorNaMemoria={this.props.valorNaMemoria} operacaoAnterior={this.props.operacaoAnterior} valorVisor={this.props.valorVisor} limparNaProximaOperacao={this.props.limparNaProximaOperacao}
                       entradaDecimal={this.props.entradaDecimal} valor="="/>
            </div>
        )
    }
}

Pronto, agora os valores estarão presentes nos botões. Finalmente vamos criar uma função para tratar os cliques nos botões e chamar as actions, fazendo a calculadora funcionar

Atualize o arquivo Botao.js com o seguinte conteúdo:

import React from 'react'
import * as Action from './../flux/CalculdoraActions'

export default class Botao extends React.Component{
    constructor(props){
        super(props)
    }

    render(){
        return (
            <div className="botao" onClick={this.click.bind(this)}>
                <p>{this.props.valor}</p>
            </div>
        )
    }

    click(e){
        switch(this.props.valor){
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                Action.atualizarVisor(this.props.valorVisor,this.props.valor
                    ,this.props.limparNaProximaOperacao,this.props.entradaDecimal)
                break;
            case '+':
            case '-':
            case '*':
            case '/':
            case '=':
                Action.executarOperacaoMatematica(this.props.valor,this.props.operacaoAnterior
                    ,this.props.valorVisor,this.props.valorNaMemoria)
                break;
            case '.':
                Action.setModoDeEntradaDecimal(true)
                break;
            case 'c':
                Action.resetar();
                break;
        }
    }
}

As alterações que fizemos no Botao foram:

1- adicionamos uma função para chamar as actions certas de acordo com o tipo de botão clicado.

click(e){
        switch(this.props.valor){
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                Action.atualizarVisor(this.props.valorVisor,this.props.valor
                    ,this.props.limparNaProximaOperacao,this.props.entradaDecimal)
                break;
            case '+':
            case '-':
            case '*':
            case '/':
            case '=':
                Action.executarOperacaoMatematica(this.props.valor,this.props.operacaoAnterior
                    ,this.props.valorVisor,this.props.valorNaMemoria)
                break;
            case '.':
                Action.setModoDeEntradaDecimal(true)
                break;
            case 'c':
                Action.resetar();
                break;
        }
    }

2 – Registramos o handler do click na div

onClick={this.click.bind(this)}

 

Conclusão

Nesse ponto você já está apto a compreender os fundamentos da arquitetura flux, e já deve ter compreendido o funcionamento básico do react. Caso você queira baixar o projeto pronto, ele está disponível em https://github.com/victorrseloy/tutorial-react-e-flux-calculadora.

Espero que tenha gostado desse mini curso, caso tenha sugestões de assuntos que gostaria de ler aqui no blog por favor envie para omestredocodigo@gmail.com.

Caso você encontre algum problema com o projeto por favor crie uma issue no github ou mande um email para omestredocodigo@gmail.com.

Não esqueça de se inscrever na nossa newsletter para receber atualizações do Mestre do Código.