Redux vs Vuex for state management in Vue.js
Introduction
Hi! This is my first post on Codementor and I hope you will like it. I 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 Redux.
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
I hope you find this post useful and I look forward to creating the next one
However, in vue js with vuex each time the page is refreshed all the store’s state gets back to the initial state. How can we store for example tokens and logged in user in vue js with vuex if it is possible ?
A store needs to be backed by something persistent. localStorage is an easy way to start.
Why does everyone compare Vuex Actions and Redux Actions? They don’t tackle the same thing at all.
Actually Vuexs mutations are the equivalent to Reduxs actions (and the connected reducer-case, which is the way it should be IMAO).
Vuexs actions are somewhat comparable to what Middleware like Redux-Thunk tries to do, composing multiple (Redux) actions in an potentially asynchronous manner.
It’s because Vuex thought things through to the end and has an official way of composing their mutations making it superior, because that way you both have a far cleaner syntax AND all the tooling is made with that in mind, while redux tooling either doesn’t support anything of these fancy things OR is fragmented between Redux-Thunk and Redux-Saga or other competing solutions.
Add to that that a large number of other features of Vuex like their modules allowing for far more options than combineReducers, their computed properties reducing state duplication, having a single source of truth and reducing computation load by memoization, them having a build in principle of state-dependent functions with method-getters and it’s simply far superior.
Hi, I’m a Redux maintainer. A few quick thoughts.
First, Redux does not require you to write multiple action types to handle loading data (ie,
"START/SUCCESS/FAILURE"
). It’s a common convention, but definitely not necessary. You’d only need to do that if you specifically want to do things like showing loaders/spinners.Second, you said that “async actions are more organized in Vue, and it doesn’t require multiple types”. However, looking at the Vue docs page at https://vuex.vuejs.org/guide/actions.html , I specifically see examples that do use that three-phase action type pattern.
Third, we have a new https://github.com/reduxjs/redux-starter-kit package that helps simplify several common use cases, including store setup (with the devtools built in), defining reducers, immutable reducer update logic, and even setting up entire “slices” of state at once. I’d encourage anyone who’s learning or using Redux to try out this package.