With the release of Vue.js 2.0 and Spark 3.0, the time had come for us to update our own Spark application. In doing so, we ran into the usual headaches and snags that you might expect from such a comprehensive upgrade. But, perhaps the biggest issue for us was Vue's deprecation of
$broadcasts for eventing. Here we had a choice. We could either create our own centralized event hub or use Vue's dedicated state manager, Vuex.
Our Vue Point
We decided to go with Vuex for a number of reasons. First, a simple event hub would quickly become unwieldy for us to use due to the complexity of our frontend components. Second, Vuex's ability to create state
Modules appealed to us because Spark isn't a single page app. So, we needed a way to keep the various states of our application separate. Lastly, using the event hub didn't seem like the Vue way to solve this problem. When we looked into some of the decisions made for Vue 2.0, we realized that the dev team had worked really hard to strip Vue down to its essence. In doing so, they moved "extra" functionality into other packages, relied on 3rd party providers, or even removed it from the ecosystem entirely (looking at you vue-resource). With this in mind, we wanted to make sure that all of the work we were doing for the migration would stay current, and so Vuex made the most sense for state management.
Once we were comfortable with our decision, it was time to hit the codebase! Now, let me show you how to add Vuex to your Spark project and give you a look at how we decided to organize everything.
Focusing Your Vue
Before we begin, make sure that you are using Spark 3.0 with Vue 2.0 already installed.
First things first, we need to install Vuex. If you check out the Vuex docs, you can see that you simply need to run
npm install vuex to add it to your project. With that done, head on over to your
resources/assets/js directory and open up
app.js. This is where you can import your Vuex store once it has been set up. How you want to set it up depends on your project's directory structure, but ours looks like this:
As you can see, we've created our own
vuex directory within
resources/assets/js. Inside of this we have our
store.js and two other directories, which you can ignore. Returning to our
app.js file, you can now see that we've added an
import from our
vuex/store.js and then added that import to Spark's global Vue instance. This makes the Vuex store available to all Vue components.
With your Vuex store imported correctly, you can now set up your store. Inside of
store.js you will need to set import Vue and Vuex at the top of the file like this:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex);
Now you can create your global Vuex store. If you're unfamilar with using Vuex, I highly recommend checking out their documentation and getting a feel for how to use it. But, at a very high level, Vuex works like this:
Image provided via the Vuex Documentation
So, your Vue components "dispatch" Actions to Vuex. The Vuex Actions then handle any kind of asynchronous activity before "committing" a Mutation. The Mutations then "mutate" the Vuex State and then everything is rendered back to your Vue components via Getters. This ensures that there is a one-way flow of data throughout your Vue application.
We represent all of this inside of our
store.js like so:
This may look confusing, but let's break it down section by section. At the top, we have our import statements, which we covered already. After that, we've defined four constants - one for each of the Vuex patterns. Inside of our
state constant, we've defined the initial state of the store with a simple count set to 0. For me, I like to think of this as analogous to a Vue component's
data. Next, we have our
getters constant. Here we're actually modifying our state by returning the
count plus 1. This
getter will be used to make the
countPlusOne available to our Vue components. Moving along, we reach our
mutations constant. Here we're just saying that when the
INCREMENT mutation is "committed", then the
count state should increment by one. Then we have our
actions constant. This
increment action can now be "dispatched" from our Vue components to "commit" the
INCREMENT mutation. Finally, we export the
Vuex.store so that it can be imported into
app.js and added to Spark's global Vue instance.
Now that our state has been set up, let's hop into a Vue component and get it interacting with the state. As you can see below, we've set up a pretty simple Vue component that will increment the count and display it.
This looks like a pretty typical Vue component, except we're doing three things to communicate with the Vuex state. First, we've imported
mapActions from our
store.js. Second, in our
computed properties, we have mapped the Getters from our state to our component. Notice that by using Object syntax, I was able to rename the
countPlusOne to simply
count. Lastly, we've mapped the Actions in the component's
method property so I can use
increment when I click the button.
Admiring the Vue With DevTools
Putting this all together, let's see how it renders on the page.
As you can expect, we have a simple button next to the count. Let's hop into our Vue devtools and see what the component and state look like there.
In our templates component, we can see that the
count is equal to 1, which is exactly what the
countPlusOne Getter was supposed to show us. Hopping over to the Vuex store, we can see that the Base State is simply the
count equal to 0, which is exactly what we defined in our
Now let's see what happens when I click the button. As we can see in our Vuex store, clicking the button triggered the
increment method that we mapped from the Vuex Actions. This in turn "committed" a Mutation, which we can see here in the Vuex store:
count state is now equal to one, which means that our computed property that we mapped from our Getters is now equal to 2.
Finally, we can see that our rendered Vue component has updated as well.
Whew! That was quite a bit of work just to get an incrementer going. But, think of how much more difficult it would be to share the
count with your other components in Vue 1.0. You'd have to
$dispatch a message
onclick up to a parent component, which was listening to it in its
event property. Then the parent would have to
$broadcast that to all of the other components that are listening for it. And then what do you do if those components have to send events now that the
count has updated? It all gets really complicated pretty quickly. But with Vuex, all we have to do in our other components is import the Getters and Actions, and then they can use the state as they see fit and it all stays synchronized. No more crazy paths of
$broadcasts to follow!
There's certainly a lot more you can do with Vuex that we didn't cover here. You can create
Modules to keep your states separated across your application, or you can fire API calls in your Actions to ensure your database keeps in sync with your application state. In future blog posts, we'll definitely cover these topics and more, but I hope that this post can get you up and running with Vuex and Spark in no time!