We just finished the middle-sized project in Vue.js, and since I was building a complex app I needed a solution to manage the app state. I spent some time playing with both Redux and Vuex. In this post, I am going to show you the main differences between those two libraries and which one I picked for my solution. Ready? Let’s go!
What is state management?
State management is a way of managing the state inside your application, making sure that your UI is always reflecting your app changes. There are multiple ways to manage the state in your app, and in many cases, you don’t need libraries like Redux or Vuex, but if your app starts to become wild and complicated, state management solution can help you evaluate those problems.
In the past, it was not uncommon to have pieces of state across our application tucked inside of controllers, services, routes, directives (AngularJS), local storage, session storage, cookies, and some other alternatives.
When the application grows, this approach is really hard to scale and this is where Flux and React stepped in.
React changed the way we approach state-management. Redux (based on Flux) is front and center of this shift as it introduced an elegant, yet profoundly simple way to manage application state. Vuex followed Redux and found the place amount Vue developers.
What is Redux?
Redux is a framework-agnostic state management library. Something that I noticed during development with React is that Redux is created with React in mind. If you’re thinking about using Vue with Redux, Vuex is probably a better state management library for maintaining performance. Here are some things to know about the two:
Actions
Let’s start with the easiest part of Redux. Actions! Actions are very simple objects with a “type” property and the minimum number of other properties and values that are necessary to explain the action we intend to make in the state.
Actions are the only way to change the state and changing state is done by “dispatching” an action using store. Here is an example:
import { createStore } from 'redux' import { ActionTypes as types } from '../constants' var defaultState = { name: '' }; function reducer(state = defaultState, action) { switch(action.type) { case types.CHANGE_NAME: return { ...state, name: action.data.name } default: return state } } // Let's create the store, and add root reducer to it var store = createStore(reducer) // This is where we dispatch action to the store store.dispatch({type: types.CHANGE_NAME, data: {name: "Petar"}})
You can see that .dispatch() takes “action” object and sends this to the reducer. If the action isn’t recognized and no change is dispatched, the state defaults to the old state.
Reducers
This is where the fun stuff happens and where we change the state in the store based on the action “type”. Reducers are pure functions that are used to implement how the next state is calculated from the current state and the action.
Since Redux has only one store, we use multiple reducers to manage parts of the global state (store). This concept allows you to have multiple parts inside your store, all managed by different reducers.
These reducers can then be combined into one root reducer to manage all of your application states. The redux store saves the complete state tree returned by the root reducer.
Store
This is a place where you store the state of your application. The store is nothing more than an object containing different properties and values. There is only one store in Redux (root store) and one root reducer. The point with the root store is that you can have multiple reducers that work on different parts of your store.
To change the state, you have to dispatch an action to it. Once you dispatched an action you can add a change listener called “subscribe”. By subscribing to the store you can check if an action is ‘dispatched’ and is your state tree changed.
While this is working, this might not be the best solution for your Vue.js project. The biggest reason why is because Redux replaces the state object on every update.
React is different from Vue in the way it processes updates: React renders a virtual DOM then calculates optimal DOM operations to make the currently rendered DOM match the new Virtual Dom. But it has no way of knowing whether a particular component needs to re-render or not based on the new data. Vue instances keep track of which bits of data they depend on to render. These instances automatically register what needs to re-render when the data changes.
Vuex
Vuex is very similar to Redux and also inspired by Flux. Unlike Redux, Vuex mutates the state rather than making the state immutable. This approach removes the need for having a reducer, so in Vuex reducers are replaced with something called Mutations.
This allows Vue.js to automatically know which components need to be re-rendered when the state changes. Instead of breaking down state logic with specialized reducers, Vuex is able to organize its state logic with stores called modules.
Mutations
The only way to change state in a Vuex store is by committing a mutation. Mutations are very similar to the events and each mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the state as the first argument:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // mutate state state.count++ } } })
You cannot directly call a mutation handler. Think of it like: “When a mutation with type increment is triggered, call this handler”. To invoke a mutation handler, you need to call store.commit with its type:
store.commit('increment')
One thing to remember is that mutations are always synchronous to ensure that the state isn’t dependent on the timing and order of unpredictable events.
Actions
Actions are similar to mutations and work like Redux actions. Actions can’t mutate the state, rather action commit mutations. One other benefit of working with Vuex actions is that actions can contain arbitrary asynchronous operations. This is great when you need to call your API and is much more organized comparing to
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
To Dispatch an action we use:
store.dispatch('increment')
Store
Vuex uses a single state tree — that is, this single object contains all your application-level state and serves as the “single source of truth”. This is very similar to Redux and the Store is the center of every Vuex application.
Since Vue instances keep track of which bits of data they depend on to render, vuex is taking advantage of that and when components get the state from the store, these components can reactively and efficiently update whenever the store’s state changes. One thing to remember is that the store’s state can only be changed by committing mutations.
The finals
While Redux and Vuex are amazing state management libraries and patterns, I prefer Vuex over Redux when working with Vue. Here are the main reasons:
- Mutations are easier to work with then Reducers
- Asynchronous actions are much more organized in Vuex. There is no need to write _ON_LOAD, _SUCCESS or _FAIL middle term state actions.
- Out of the box dev tools
- Much easier setup. It is easy as Vue.use(Vuex)
- Vuex takes advantage of Vue.js uniqueness