You may not need vuex. Here are some alternatives
Published 3/23/2020
Let's take a look at two approaches you can take before reaching for vuex!
First approach (for simpler websites)
Let's consider a typical SPA that is generally separated by page (vue-router) much like a traditional website.
Your application probably has the following three types of state:
- Local state exclusive to a component
- Global state that needs to be shared across routes
- Shared state within a route
The first type of state is very good for, well, local state. Maybe it is exclusive to this component or only goes one or two layers deep.
As for the second type of state, Vue allows you to define data, methods and everything else you might need in the Vue root.
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>',
data() {
return {
isLoggedIn: false
}
}
})
Within each component you can then access is via $root
, e.g. this.$root.isLoggedIn
.
This is very useful for things like the user instance.
But what about the third type of state? Shared state within a route. Without state management you are probably going to pass down props multiple components deep. And you know what, in many cases, that's just fine! But here's a little trick if you really want to share state within a route.
It turns out Vue actually allows you to access all the state and methods from the route root within any of its subcomponents.
Say the route root exposes the data { isOpen: false }
.
Within any subcomponent you can access this property like this:
this.$route.matched[0].instances.default.isOpen
Okay, that's very very verbose! But it works!
Let me introduce to you a small plugin I wrote that turns the above into:
this.$routeRoot.isOpen
I haven't actually used this in any projects, but I love its simplicity.
- vuex redefines the way you write state, methods, computed fields, everything really. With this approach, you can leverage your existing knowledge. You still write the state inside "data", you still have your methods, computed fields, literally nothing changes. There is only one paradigm.
- with vuex, all your state is global. Sure you can define modules, but you still manually have to select from which module to get your state even when you are already inside a specific route. When I am on the settings page, I don't care what state was last used on the profile page, etc.
Check out my e-book!
There is also a second way you can use this plugin. Instead of registering it, you may map properties from the two roots to your local component if you prefer that.
import { mapRoot } from 'vue-route-root'
export default {
// first one from $root, second and third one one from route root
computed: mapRoot(['user', 'isOpen', 'setOpen']),
}
This has the benefit that you can't just mutate root state from within a child. If you want to mutate state, you need to call a method. This forces you to place all state mutations in the root and makes sure they are not scattered across the application.
Cautions with this approach
The vue documentation does not recommend the usage of $root because it "scales poorly". This is very much true if you are not careful. But think about it, $root and $routeRoot are pretty much structure-less. Guess what happens when you are not careful with a very very structured tool like vuex! If you want to change such an implementation, you are required to first destructure it before you can change it.
But I wholeheartedly agree that this approach has its limits.
For example, it forces you to have all logic inside the root components, making them rather big. You also have to be careful to never ever access $routeRoot
within a dumb / reusable component.
Second approach
Say your application is a little more complex or follows a different approach.
There is another plugin I created which takes more the form of vuex and which doesn't make use of Vue's $root or route roots. I call it vue-blick.
First, create your store in a simple .js file
// store/alert-store.js
import { create } from 'vue-blick'
export default create({
message: 'Hello', // state
get reversedMessage() { // computed fields/getters
return this.message.split('').reverse().join('')
},
async setMessage(message) { // methods/actions
// await fetch(...)
this.message = message
}
})
And then you can use it in any component like this
<template>
<div>alert: {{ message }}</div>
<div>reversed alert: {{ reversedMessage }}</div>
<button @click="setMessage('World')">alert!</button>
</template>
<script>
import alertStore from './store/alert-store'
export default {
mixins: [ alertStore.map('message', 'reversedMessage', 'setMessage') ]
}
</script>
What I like about this approach:
- while it is closer to more traditional state management, there are zero terminologies like actions, getters, mutations, reducers, selectors, dispatch, commit, action types, reduce reducers, etc.
- You still access properties within a computed field or method using
this
, exactly the same as in a Vue component mixins: [ userStore.map('message', 'reversedMessage', 'setMessage') ]
: Only one method to map state, getters and methods. And you map it in one place only... Without any destructuring!
Just like the vuex documentation states: You should weigh the trade-offs and make decisions that fit the development needs of your app. Maybe instead, you can let the server hold such state, like with inertia.js. Or Maybe vuex is exactly what you need!
If you like this sort of article, make sure to also check out my article on simplifying vue components.