menu

SHARKLABS

Comparação: Vuex vs Redux

/
/
Comparação: Vuex vs Redux
bookmark React.js, Vue.js access_time

Vue.js e React.js

Se você acompanha meu blog, já sabe que eu trabalho com Vue.js a alguns anos, mas recentemente estou participando de um projeto com React.js e por isso venho escrevendo alguns artigos comparando essas ferramentas.

Hoje é o dia de comparar Vuex com Redux, que são as principais bibliotecas de para gerenciar o estado da aplicação.

A principal característica das duas bibliotecas é que ambas se baseiam na arquitetura Flux. De uma maneira bem resumida, a arquitetura Flux se propõe a armazenar alguns dados em uma Store centralizada e somente algumas funções podem alterar essa "Store".

Desta maneira, as requisições de alteração são centralizadas e enfileiradas, algo que é muito interessante quando temos vários componentes alterando os mesmos dados simultaneamente.

Antes de continuar é importante destacar que meu objetivo não é dizer qual é melhor ou pior, mas sim mostrar como cada biblioteca funciona.

Exemplo Prático

Eu vou criar um exemplo prático com ambas as bibliotecas. Basicamente esse exemplo terá dois componentes:

  • Todo: Formulário para inserir tarefas a fazer.
  • Todos: Listagem de todas as tarefas com duas ações (Marcar como feito/desfeito e Excluir).

Obviamente os dados estarão centralizados em uma Store que será compartilhada com estes componentes.

Exemplo com Vuex

Antes de continuar, você precisa de um projeto com Vue.js e Vuex configurados. Se você tem dúvidas sobre isso, clique aqui.

Veja como fica o arquivo "/src/main.js":

import Vue from 'vue';
import App from './App.vue';
import store from './store/index.js';

Vue.config.productionTip = false;

new Vue({
  store,
  render: (h) => h(App),
}).$mount('#app');

Arquivo "/src/store/index.js":

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    todos: [],
  },
  mutations: {
    addTodo(state, todo) {
      state.todos.push(todo);
    },
    changeDoneTodo(state, { index, done }) {
      state.todos[index].done = done;
    },
    deleteTodo(state, { index }) {
      state.todos.splice(index, 1);
    },
  },
});

Arquivo "/src/App.vue":

<template>
  <div id="app">
    <Todo />
    <Todos />
  </div>
</template>

<script>
import Todo from '@/components/Todo.vue';
import Todos from '@/components/Todos.vue';

export default {
  name: 'App',
  components: { Todo, Todos },
};
</script>

Arquivo "/src/components/Todo.vue":

<template>
  <div>
    <input type="text" placeholder="text" v-model="todo.text" />
    <button style="margin-left: 5px" @click="insertTodo">
      Add Todo
    </button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex';

export default {
  name: 'Todo',
  data: () => ({
    todo: {
      text: null,
      done: false,
    },
  }),
  methods: {
    ...mapMutations(['addTodo']),
    insertTodo() {
      this.addTodo(this.todo);
      this.todo = { text: null, done: false };
    },
  },
};
</script>

Arquivo "/src/components/Todos.vue":

<template>
  <div>
    <ul>
      <li v-for="(todo, index) in todos">
        <span>{{ todo.text }}</span>
        <button v-if="todo.done" style="margin-left: 5px" @click="changeDoneTodo({ index, done: false })">
          Set as Undone
        </button>
        <button v-else style="margin-left: 5px" @click="changeDoneTodo({ index, done: true })">
          Set as Done
        </button>
        <button style="margin-left: 5px" @click="deleteTodo({ index })">
          Delete
        </button>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex';

export default {
  name: 'Todos',
  computed: {
    ...mapState({
      todos: (state) => state.todos,
    }),
  },
  methods: {
    ...mapMutations(['changeDoneTodo', 'deleteTodo']),
  },
};
</script>

Exemplo com Redux

Antes de continuar, você precisa de um projeto com React.js e Redux configurados. Se você tem dúvidas sobre isso, clique aqui para entender como instalar o React.js e clique aqui para entender como instalar o Redux.

Veja como fica o arquivo "/src/index.js":

import React from 'react';
import ReactDOM from 'react-dom';

import { createStore } from 'redux';
import { Provider } from 'react-redux';

import App from './components/App.js';

import reducers from './reducers/index.js';
const store = createStore(reducers);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

Arquivo "/src/reducers/index.js":

import { combineReducers } from 'redux';

const todos = (state = [], action) => {
  const actions = {
    ADD_TODO() {
      return [
        ...state,
        {
          ...action.todo,
        },
      ];
    },
    CHANGE_DONE_TODO() {
      const newState = [...state];
      newState[action.index].done = action.done;
      return newState;
    },
    DELETE_TODO() {
      const newState = [...state];
      newState.splice(action.index, 1);
      return newState;
    },
  };
  return actions[action.type] ? actions[action.type]() : state;
};

export default combineReducers({ todos });

Arquivo "/src/components/App.js":

import React from 'react';

import TodosContainer from '../container/TodosContainer.js';
import TodoContainer from '../container/TodoContainer.js';

export default function App() {
  return (
    <div>
      <TodoContainer />
      <TodosContainer />
    </div>
  );
}

Arquivo "/src/container/TodoContainer.js":

import { connect } from 'react-redux';
import Todo from '../components/Todo.js';
import { addTodo } from '../actions/index.js';

const mapDispatchToProps = (dispatch) => ({
  addTodo: (text) => dispatch(addTodo(text)),
});

export default connect(null, mapDispatchToProps)(Todo);

Arquivo "/src/container/TodosContainer.js":

import { connect } from 'react-redux';
import Todos from '../components/Todos.js';
import { doneTodo, undoneTodo, deleteTodo } from '../actions/index.js';

const mapStateToProps = (state) => ({
  todos: state.todos,
});

const mapDispatchToProps = (dispatch) => ({
  doneTodo: (index) => dispatch(doneTodo(index)),
  undoneTodo: (index) => dispatch(undoneTodo(index)),
  deleteTodo: (index) => dispatch(deleteTodo(index)),
});

export default connect(mapStateToProps, mapDispatchToProps)(Todos);

Arquivo "/src/actions/index.js":

export const addTodo = (text) => ({
  type: 'ADD_TODO',
  todo: {
    text,
    done: false,
  },
});

export const doneTodo = (index) => ({
  type: 'CHANGE_DONE_TODO',
  index,
  done: true,
});

export const undoneTodo = (index) => ({
  type: 'CHANGE_DONE_TODO',
  index,
  done: false,
});

export const deleteTodo = (index) => ({
  type: 'DELETE_TODO',
  index,
});

Arquivo "/src/components/Todo.js":

import React, { useState } from 'react';

export default function Todo({ addTodo }) {
  const [text, setText] = useState('');
  return (
    <div>
      <input type="text" placeholder="text" onChange={(e) => setText(e.target.value)} value={text} />
      <button
        style={{ marginLeft: '5px' }}
        onClick={() => {
          addTodo(text);
          setText('');
        }}
      >
        Add Todo
      </button>
    </div>
  );
}

Arquivo "/src/components/Todos.js":

import React from 'react';

export default function Todos({ todos, doneTodo, undoneTodo, deleteTodo }) {
  const actionDone = (done, index) => {
    if (done) {
      return (
        <button style={{ marginLeft: '5px' }} onClick={() => undoneTodo(index)}>
          Set as Undone
        </button>
      );
    }
    return (
      <button style={{ marginLeft: '5px' }} onClick={() => doneTodo(index)}>
        Set as Done
      </button>
    );
  };

  return (
    <div>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            <span>{todo.text}</span>
            {actionDone(todo.done, index)}
            <button style={{ marginLeft: '5px' }} onClick={() => deleteTodo(index)}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Diferenças entre Vuex e Redux

A primeira coisa que precisamos destacar é a diferença entre nomenclaturas.

Por exemplo no Vuex temos:

  • State: Estado inicial da Store.
  • Mutation: Método síncrono para alteração da Store.
  • Action: Método assíncrono que chama uma mutation para alterar a Store.

Já no Redux temos:

  • Reducer: Método síncrono para alteração da Store.
  • Action: Funciona como uma "fábrica" de objetos para serem passados para os Reducers.

Veja que o conceito de Action entre as bibliotecas é diferente. No Redux a Action não tem contato nenhum com o Reducer e nem com o componente, ela somente fabrica o objeto de parâmetro.

Por padrão o Redux não traz uma solução para alterações assíncronas, mas você pode usar a biblioteca Redux-Thunk para tratar isso.

Sobre o estado inicial da Store do Redux, isso normalmente é configurado como parâmetro default do Reducer:

const todos = (/* ATTENTION HERE */ state = [], action) => {};

Vuex vs Redux: Imutabilidade

Outra diferença muito importante é que o Redux tem uma Store imutável, ou seja, quando é necessário mudar alguma coisa na Store, um novo objeto é criado e o antigo é descartado.

Por outro lado, a Store do Vuex é mutável, ou seja, você pode alterar os dados do State. Inclusive há uma Issue no repositório do Vuex que discute esse tema.

Você deve ter percebido que as Mutations do Vuex tem um papel muito parecido com os Reducers do Redux, mas a grande diferença é a questão da imutabilidade. As Mutations do Vuex alteram o State e os Reducers do Redux sempre criam um novo objeto com as alterações.

Vuex vs Redux: Usando Dentro dos Componentes

Mais um ponto que gera bastante dúvida, é como usar os dados da Store dentro dos componentes.

Para usar o Vuex dentro dos componentes existem duas maneiras:

Já para usar o Redux dentro dos componentes é um pouco diferente. Você precisa usar a função connect que vai passar os dados do Redux para o componente como um argumento (estou falando das props).

Só que não é recomendável sair usando a função connect para todos os lados. O ideal é criar um arquivo de container que conecta o componente com o Redux.

É nesse container que você vai chamar o Reducer e passando o resultado da Action como parâmetro. Tudo isso você encapsula dentro uma função mais fácil de ser utilizada dentro do componente.

Na minha opinião, estas são as diferenças mais importantes. As outras diferenças estão mais relacionadas a sintaxe e detalhes de cada framework.

Por fim

Vale destacar que neste artigo eu utilizei as seguintes versões:

Dúvidas ou sugestões é só entrar em contato. Abraço.

Autor
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." Martin Fowler