Translate

Tuesday, 18 June 2019

Getting Started with Vuex: a Beginner’s Guide

Getting Started with Vuex: a Beginner’s Guide

In single-page applications, the concept of state relates to any piece of data that can change. An example of state could be the details of a logged-in user, or data fetched from an API.

Handling state in single-page apps can be a tricky process. As an application gets larger and more complex, you start to encounter situations where a given piece of state needs to be used in multiple components, or you find yourself passing state through components that don’t need it, just to get it to where it needs to be. This is also known as “prop drilling”, and can lead to some unwieldy code.

Vuex is the official state management solution for Vue. It works by having a central store for shared state, and providing methods to allow any component in your application to access that state. In essence, Vuex ensures your views remain consistent with your application data, regardless of which function triggers a change to your application data.

In this article, I’ll offer you a high-level overview of Vuex and demonstrate how to implement it into a simple app.

A Shopping Cart Example

Let’s consider a real-world example to demonstrate the problem that Vuex solves.

When you go to a shopping site, you’ll usually have a list of products. Each product has an Add to Cart button and sometimes an Items Remaining label indicating the current stock or the maximum number of items you can order for the specified product. Each time a product is purchased, the current stock of that product is reduced. When this happens, the Items Remaining label should update with the correct figure. When the product’s stock level reaches 0, the label should read Out of Stock. In addition, the Add to Cart button should be disabled or hidden to ensure customers can’t order products that are currently not in inventory.

Now ask yourself how you’d implement this logic. It may be trickier than you think. And let me throw in a curve ball. You’ll need another function for updating stock records when new stock comes in. When the depleted product’s stock is updated, both the Items Remaining label and the Add to Cart button should be updated instantly to reflect the new state of the stock.

Depending on your programming prowess, your solution may start to look a bit like spaghetti. Now, let’s imagine your boss tells you to develop an API that allows third-party sites to sell the products directly from the warehouse. The API needs to ensure that the main shopping website remains in sync with the products’ stock levels. At this point you feel like pulling your hair out and demanding why you weren’t told to implement this earlier. You feel like all your hard work has gone to waste, as you’ll need to completely rework your code to cope with this new requirement.

This is where a state management pattern library can save you from such headaches. It will help you organize the code that handles your front-end data in a way that makes adding new requirements a breeze.

Prerequisites

Before we start, I’ll assume that you:

  • have a basic knowledge of Vue.js
  • are familiar with ES6 and ES7 language features

You’ll also need to have a recent version of Node.js that’s not older than version 6.0. At the time of writing, Node.js v10.13.0 (LTS) and npm version 6.4.1 are the most recent. If you don’t have a suitable version of Node installed on your system already, I recommend using a version manager.

Finally, you should have the most recent version of the Vue CLI installed:

npm install -g @vue/cli

Build a Counter Using Local State

In this section, we’re going to build a simple counter that keeps track of its state locally. Once we’re done, I’ll go over the fundamental concepts of Vuex, before looking at how to rewrite the counter app to use Vue’s official state management solution.

Getting Set Up

Let’s generate a new project using the CLI:

vue create vuex-counter

A wizard will open up to guide you through the project creation. Select Manually select features and ensure that you choose to install Vuex.

Next, change into the new directory and in the src/components folder, rename HelloWorld.vue to Counter.vue:

cd vuex-counter
mv src/components/HelloWorld.vue src/components/Counter.vue

Finally, open up src/App.vue and replace the existing code with the following:

<template>
  <div id="app">
    <h1>Vuex Counter</h1>
    <Counter/>
  </div>
</template>

<script>
import Counter from './components/Counter.vue'

export default {
  name: 'app',
  components: {
    Counter
  }
}
</script>

You can leave the styles as they are.

Creating the Counter

Let’s start off by initializing a count and outputting it to the page. We’ll also inform the user whether the count is currently even or odd. Open up src/components/Counter.vue and replace the code with the following:

<template>
  <div>
    <p>Clicked  times! Count is .</p>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  data: function() {
    return {
      count: 0
    };
  },
  computed: {
    parity: function() {
      return this.count % 2 === 0 ? 'even' : 'odd';
    }
  }
}
</script>

As you can see, we have one state variable called count and a computed function called parity which returns the string even or odd depending on the whether count is an odd or even number.

To see what we’ve got so far, start the app from within the root folder by running npm run serve and navigate to http://localhost:8080.

Feel free to change the value of the counter to show that the correct output for both counter and parity is displayed. When you’re satisfied, make sure to reset it back to 0 before we proceed to the next step.

Incrementing and Decrementing

Right after the computed property in the <script> section of Counter.vue, add this code:

methods: {
  increment: function () {
    this.count++;
  },
  decrement: function () {
    this.count--;
  },
  incrementIfOdd: function () {
    if (this.parity === 'odd') {
      this.increment();
    }
  },
  incrementAsync: function () {
    setTimeout(() => {
      this.increment()
    }, 1000)
  }
}

The first two functions, increment and decrement, are hopefully self-explanatory. The incrementIfOdd function only executes if the value of count is an odd number, whereas incrementAsync is an asynchronous function that performs an increment after one second.

In order to access these new methods from the template, we’ll need to define some buttons. Insert the following after the template code which outputs the count and parity:

<button @click="increment" variant="success">Increment</button>
<button @click="decrement" variant="danger">Decrement</button>
<button @click="incrementIfOdd" variant="info">Increment if Odd</button>
<button @click="incrementAsync" variant="warning">Increment Async</button>

After you’ve saved, the browser should refresh automatically. Click all of the buttons to ensure everything is working as expected. This is what you should have ended up with:

See the Pen Vue Counter Using Local State by SitePoint (@SitePoint) on CodePen.

The counter example is now complete. Let’s move and examine the fundamentals of Vuex, before looking at how we would rewrite the counter to implement them.

How Vuex Works

Before we go over the practical implementation, it’s best that we acquire a basic grasp of how Vuex code is organized. If you’re familiar with similar frameworks such as Redux, you shouldn’t find anything too surprising here. If you haven’t dealt with any Flux-based state management frameworks before, please pay close attention.

The Vuex Store

The store provides a centralized repository for shared state in Vue apps. This is what it looks like in its most basic form:

// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    // put variables and collections here
  },
  mutations: {
    // put sychronous functions for changing state e.g. add, edit, delete
  },
  actions: {
    // put asynchronous functions that can call one or more mutation functions
  }
})

After defining your store, you need to inject it into your Vue.js application like this:

// src/main.js
import store from './store'

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

This will make the injected store instance available to every component in our application as this.$store.

Working with State

Also referred to as the single state tree, this is simply an object that contains all front-end application data. Vuex, just like Redux, operates using a single store. Application data is organized in a tree-like structure. Its construction is quite simple. Here’s an example:

state: {
  products: [],
  count: 5,
  loggedInUser: {
    name: 'John',
    role: 'Admin'
  }
}

Here we have products that we’ve initialized with an empty array, and count, which is initialized with the value 5. We also have loggedInUser, which is a JavaScript object literal containing multiple fields. State properties can contain any valid datatype from Booleans, to arrays, to other objects.

There are multiple ways to display state in our views. We can reference the store directly in our templates using $store:

<template>
  <p></p>
</template>

Or we can return some store state from within a computed property:

<template>
  <p></p>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
}
</script>

Since Vuex stores are reactive, whenever the value of $store.state.count changes, the view will change as well. All this happens behind the scenes, making your code look simple and cleaner.

The mapState Helper

Now, suppose you have multiple states you want to display in your views. Declaring a long list of computed properties can get verbose, so Vuex provides a mapState helper. This can be used to generate multiple computed properties easily. Here’s an example:

<template>
  <div>
    <p>Welcome, .</p>
    <p>Count is .</p>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: mapState({
    count: state => state.count,
    loggedInUser: state => state.loggedInUser
  })
}
</script>

Here’s an even simpler alternative where we can pass an array of strings to the mapState helper function:

export default {
  computed: mapState([
    'count', 'loggedInUser'
  ])
}

This version of the code and the one above it do exactly the same thing. You should note that mapState returns an object. If you want to use it with other computed properties, you can use the spread operator. Here’s how:

computed: {
  ...mapState([
    'count', 'loggedInUser'
  ]),
  parity: function() {
    return this.count % 2  === 0 ? 'even' : 'odd'
  }
}

The post Getting Started with Vuex: a Beginner’s Guide appeared first on SitePoint.



No comments:

Post a Comment