Chapter 6. Understanding Vuex

This chapter covers

  • What Vuex is
  • What state means
  • How to use Vuex in a project

Vuex is a large topic. Before you learn how to test Vuex in an application in chapter 7, you need to understand the fundamentals of it. In this chapter, you’ll learn what Vuex is and how it makes it easier to manage data in an app.

Note

If you’re already familiar with Vuex, you can skip ahead to the next chapter.

As an app grows in size, so does the data that the app uses. Making sure data stored in different components stays in sync is challenging and can lead to difficult-to-track bugs. Vuex is the solution to that problem.

Vuex is described as a state management library. That description isn’t useful if you don’t know what state means, so the first section of this chapter is about understanding state in the context of a Vue application.

Note

If you’ve used Redux before, a lot of the Vuex concepts will be familiar to you. The main difference between Vuex and Redux is that Vuex mutates the store state, whereas Redux creates a new store state on every update.

After you understand what state is, I’ll explain to you the problem Vuex solves. Finally I’ll teach you the technical details of Vuex—what a Vuex store is and what parts make up a Vuex store.

First, you need to understand state.

6.1. Understanding state

People use the term state to refer to lots of different concepts, and if you Google what state is, you can end up confused. I had a hard time understanding what it was when I was learning to program.

In the context of Vue applications, state is the data that is currently stored in a running application. Imagine a Gmail inbox. If you want to delete some email messages from your inbox, you would click the check box next to the messages you wish to delete. You wouldn’t refresh the page when all the emails were checked, because you know you would lose the data about which check boxes were selected. These selected check boxes are part of the state of the page, and refreshing the page would lose that state.

To look at a more technical example, imagine a Counter Vue component that renders a count value. It has a button that increments the counter value when it’s clicked. When the component is mounted, clicking the button increments the count value rendered in the DOM, as shown in the next listing.

Listing 6.1. Counter component
<template>
  <div>
    <h1>{{count}}</h1>
    <button @click="count++">increment</button>     1
  </div>
</template>

<script>
  export default {
    data: () => ({                                  2
      count: 0
    })
  }
</script>

  • 1 Renders the count value
  • 2 The initial state of the app

The current value of count is the state of the component instance. You might have 10 different counter components in your application, and each of them could have a different count value. Each of those counter instances has a different state.

In Vue, the state of an application describes how an application should be rendered. In fact, state is the main reason that JavaScript frameworks like Vue exist. JavaScript frameworks make it easy to keep the view layer of an application in sync with the application state.

A good definition is that state is the current value of data inside a running application. It could be data returned from an API call, data triggered by user interactions, or data generated by the application.

Now that you understand state, you’ll be able to understand what Vuex does for you and the problem it solves.

6.2. The problem Vuex solves

Coordinating state between components can be tricky. If components rely on the same data but don’t communicate with each other when that data changes, you can end up with bugs.

Let’s use a real-world example. Imagine an outdoor store with two employees, Nick and Anna. At the beginning of the day, they each count how many tents they have in stock and find there are three tents to sell.

Nick and Anna spend the day on the shop floor, Nick sells one tent, and Anna sells two. Nick thinks two tents are left—he sold one and there were three this morning. Anna thinks there’s one left, because she sold two. In fact, no tents are left.

When a customer comes in and asks to buy a tent from Nick, he makes the sale, because he thinks there are still two in stock. The customer wants the tent mailed to him, so Nick takes payment from the customer without checking whether any tents remain in stock. When he goes out back, he finds out there are no tents left, and he has to make an embarrassing call to the customer.

The issue here is that Nick and Anna both had a different value for the number of tents in stock. They both had their own state. The problem was, their states became out of sync.

This can happen in a Vuex application when two components have their own state for the same data. State changes in one component might not change the value of a related state in another component—either because there is a bug or because someone forgot to write the code to keep the data in sync.

One solution for Nick and Anna is to use a computer to track how many tents are in stock. When one of them makes a sale, they enter it into the computer, and the computer deducts one from the total number of tents remaining. Now when a customer asks Nick if she can buy a tent, Nick can check the computer system and be sure there is a tent left to sell before he takes a payment.

In an application, Vuex is like the computer. It’s a central source of truth that stores the state the application uses to render.

In a Vuex application, components get data from a Vuex store. When the components need to update the state, they make changes to the store state, which will cause all components that depend on the data to rerender with the new data. By using a Vuex store, you avoid the problem of data getting out of sync between components.

Definition

The Vuex store is the Vuex container that includes the state and the methods to interact with the state.

Now that you’ve seen the problem Vuex solves in the abstract, you can learn how to implement Vuex.

6.3. Understanding the Vuex store

At the heart of every Vuex app is the store.

Note

In this book, when I talk about the store, I’m talking about the Vuex store.

To understand the store, you need to understand a core concept that Vuex follows—one-way data flow. One-way data flow means that data can flow in only one direction. The benefit of one-way data flow is that it’s easier to track where the data in an app is coming from. It simplifies the application lifecycle and avoids complicated relationships between components and the state.

Note

There’s a great talk from Facebook about the benefits of one-way data flow on: http://mng.bz/y12E.

I’ll show you how to refactor a Counter component to use Vuex. There’s no need to copy the code. The purpose of the Counter component is to demonstrate how Vuex fits into an app.

You can see the Counter component in listing 6.2. All the state it uses is local, if you update the count value; it’s updated only inside the scope of the component. You can refactor it to use a count value from a store state. That way, any other component that wants to use the count value can access the count value from the store.

Listing 6.2. Counter component
<template>
  <div>
    {{count}}
    <button @click="count++">Increment</button>
  </div>
</template>

<script>
  export default {
    data: () => ({
      count: 3
    })
  }
</script>

To use Vuex, you need to create a Vue store with some initial state.

6.3.1. Creating a store

To use Vuex, you need to install Vuex with npm, create a store, and pass it to the root Vue instance. The code would look like the following listing.

Listing 6.3. Creating a Vuex store
// ..
Vue.use(Vuex)                    1

const store = new Vuex.Store({   2
  state: {                       3
    count: 0
  }
})

new Vue({
  store,                         4
  // ..
})

  • 1 Installs Vuex on Vue
  • 2 Creates a store instance
  • 3 Sets the initial state
  • 4 Passes the store instance to the Vue instance

After you’ve created a store and passed it to the Vue instance, you can access it from inside a component. If you refactored the Counter component to read the count value from the store, it would look like the next code sample.

Listing 6.4. Using Vuex in a component
<template>
  <div>
    {{$store.state.count}}                                     1
    <button @click="$store.state.count++">Increment</button>   2
  </div>
</template>

  • 1 Uses the count value from the store
  • 2 Increments the count value in the store

The refactored component is implementing the same functionality as the original Counter component, but it commits a Vuex faux pas. The component mutates the state directly in the button-click handler.

As well as being a library, Vuex is also a pattern to follow. In the Vuex pattern you should never directly mutate the state; instead, you should change the state with Vuex mutations.

6.3.2. Understanding Vuex mutations

Although components can read directly from the store state, in the Vuex pattern, components should never write to the state directly. To update the store state, you should commit mutations.

commit is a Vuex function that you call with a mutation name and optional value; the commit function then calls the mutation with the state. For example, you could commit an increment mutation to increment the count value in the store as follows:

$store.commit('increment')

Figure 6.1 shows the Vuex pattern. The main benefit of this pattern is that it makes it possible to track state changes in the application using the Vue dev tools plugin.

Figure 6.1. One-way data flow

In the Counter component example, you need to create a mutation to update the state.count value. You add mutations inside the object used to create the store. The new store with an increment mutation would look like the next listing.

Listing 6.5. Adding a mutation
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {         1
      state.count++
    }
  }
})

  • 1 Defines an increment mutation

After you’ve defined a mutation on the store, you can commit the increment mutation from the Counter component. The commit function calls the mutation that matches the string it was called with and calls the mutation with the state object. You never call a mutation directly. In the next listing, you can see the Counter component committing an increment mutation.

Listing 6.6. Committing a mutation
<template>
  <div>
    {{$store.state.count}}
    <button @click="$store.commit('increment')">Increment</button>     1
  </div>
</template>

  • 1 Commits an increment mutation

You use mutations to update the store state. A few features to note about mutations follow:

  • Mutations edit the state object directly.
  • Mutations must be called with the commit function.
  • In the Vuex pattern, mutations are the only way to make changes to a store state.
  • Mutations must be synchronous—they can’t contain actions like API calls or database connections.

Picture the Vuex store as a bank. In a bank, the tellers are the only people who can withdraw or deposit money to an account. Bank tellers are like mutations—they can edit the state directly. When you want to change the amount of money in an account by withdrawing or depositing, you need to ask the teller to change the value for you.

To keep mutations trackable in the Vue dev tools, mutations must be synchronous. If you want to edit the state asynchronously, you can use actions.

6.3.3. Understanding Vuex actions

You can think of Vuex actions as asynchronous mutations, although there is a bit more to them than that. Suppose you need to make an AJAX call to fetch data to commit to the Vuex store. You could do this inside a component method, as shown in the following code sample.

Listing 6.7. Committing mutations asynchronously
// ..
methods: {
  fetchItems () {
    this.$store.commit('fetchItems')
    fetch('https://endpoint.com/items')                                   1
      .then(data => this.$store.commit('fetchItemsSuccess', data.json())) 2
      .catch(() => this.$store.commit('fetchItemsFailure'))               3
  }
}
// ..

  • 1 Makes a get request to an endpoint
  • 2 Commits data if the request is successful
  • 3 Commits a failure if the request fails

What if you wanted to call this action inside another component? You’d have to copy the code, which isn’t ideal. Instead, you could create a Vuex action that performs the same functionality. You could refactor fetchItems and add it as an action in a store, as shown in the next listing. Note that actions receive a context object. This context object exposes the same set of methods/properties on the store instance.

Note

You can read more about the context object in the Vuex docs—https://vuex.vuejs.org/guide/actions.html.

Listing 6.8. Committing mutations inside a Vuex action
const store = new Vuex.Store({
  state: {
    // ..
  },
  mutations: {
    // ..
  },
  actions: {
    fetchItems (context) {                                             1
      context.commit('fetchItems')
      fetch('https://endpoint.com/items')
        .then(data => context.commit('fetchItemsSuccess'), data.json   2
        .catch(() => context.commit('fetchItemsFailure'))              3
    }
  }
})

  • 1 Defines a fetchItems action, which receives a context object
  • 2 Commits a fetchItemsSuccess mutation if the fetch call is successful
  • 3 Commits a fetchItemsFailure mutation if the fetch call fails

Then you could dispatch the action inside a component with the store dispatch method, shown next. dispatch is similar to the commit method, but you use it for actions.

Listing 6.9. Dispatching an action
methods: {
  fetchItems () {
    this.$store.dispatch('fetchItems')
  }
}

To continue with the bank metaphor, sending a check to your bank is like an action. After you’ve posted the check, you can carry on with your day, safe in the knowledge that after the bank teller receives the check in the mail, they will deposit the check into your account, which will update the amount of money you have.

The key features of actions follow:

  • Actions are asynchronous.
  • Actions can commit mutations.
  • Actions have access to the store instance.
  • Actions must be called with the dispatch function.

Vuex actions are like mutations, except they can be asynchronous. Instead of editing the store directly, they can commit mutations (figure 6.2). Actions are useful for when you want to perform an asynchronous task, like an API call.

Figure 6.2. Dispatching actions in the Vuex lifecycle

The final part of a store for you to learn is Vuex getters.

6.3.4. Understanding Vuex getters

Vuex getters are like computed properties for stores. They reevaluate their value only when the data they depend on has changed.

Note

Computed properties are properties of Vue components that update only when the data they depend on has changed. You can read about computed properties in the Vue docs—https://vuejs.org/v2/guide/computed.html.

Imagine you have an array of product objects in a store state. Some of those product objects have a show property set to false. You could create a getter function to return all the products with a show property set to true, as shown in the next listing.

Listing 6.10. A getter
const store = new Vuex.Store({
  state: {
    // ..
  },
  mutations: {
    // ..
  },
  getters: {
    filteredProducts (state) {                               1
      return state.products.filter(product => product.show)
    }
  }
})

  • 1 Defines a filteredProducts getter that returns all items in the state.products array with a truthy show value

Getters are called only when the data they depend on changes. If the data hasn’t changed, they return their previous calculations. You can think of them as cached functions. Remember the bank example from earlier? Imagine you wanted to know how much you have in your combined bank accounts. The total amount in all accounts isn’t saved as a value in the computer system, so the teller gets all the account totals, adds them together to get the overall total, and writes the total down on a piece of paper. Now you know how much you have, but it took the teller a few minutes to calculate it.

Later that day, you come back to the bank and want to know the total again. The teller knows you haven’t withdrawn or deposited any money, so they pick up the piece of paper and show it to you. This time, they didn’t need to spend five minutes calculating the total balance. This is how getters work. They perform logic on your data and then save (or cache) the value so that the value doesn’t need to be recomputed if the data it relies on hasn’t changed.

If you’ve withdrawn from your account since the time the teller wrote down your account total, the teller will need recalculate to make sure the value is up to date. In the same way, if a dependency of a getter updates, the getter will reevaluate the value (figure 6.3).

Figure 6.3. Computing data with getters

You can access getters inside components on the $store instance. You can see an example in the next listing of what this might look like.

Listing 6.11. Using a getter inside a component
<template>
  <div>
    <div v-for="product in $store.getters.filteredProducts">      1
    {{product.id}}
    </div>
  </div>
</template>

  • 1 Loops through items in the filteredProducts getter
Note

You can read a more in-depth explanation of the Vuex store architecture in the Vuex docs—https://vuex.vuejs.org/en/intro.html.

Now you’ve seen all the parts of a Vuex store. In the next chapter, you’ll learn how to write tests for a Vuex store, and components that use Vuex. You’ll write tests for getters, actions, and mutations and learn how to test against a running store instance.

Summary

  • Vuex is a state-management library that solves the problem of coordinating state between components.
  • Vuex uses a one-way data flow pattern to make the flow of data easy to reason about.
  • The Vuex store is built from state, mutations, actions, and getters.
  • The state contains the data used to render the application
  • Mutations are used to change the store state.
  • Actions can be asynchronous. Actions are usually used to commit mutations after an API call has finished
  • Getters are functions used to compute values using data from the store.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.4.239