The book "Vue.js in action"

    imageHi, habrozhiteli! The purpose of this book is to give you the knowledge with which you will not hesitate to join any project using this library. The book is intended for everyone who is interested in learning Vue.js and has experience with JavaScript, HTML and CSS. You are not required to have a deep knowledge of this area, but understanding the basics, such as arrays, variables, loops, and HTML elements, will not hurt.

    Under the cut is a passage in the form of the Vuex chapter, describing: what is the condition; the use of getters; implementation of mutations; adding actions; work with Vuex helper methods; modules and project setup.

    10.1. Why do we need Vuex


    The Vuex library manages state. It stores it centrally, which makes it easy to access any components to it. Status is information or data that supports the application. This is important because we need a reliable and understandable mechanism for working with this information.

    If you already have experience working with other frameworks for creating single-page applications, some of these concepts may seem familiar. For example, React uses a similar state management system called Redux. Vuex and Redux are influenced by the Flux project. This is the architecture proposed by Facebook, which is designed to simplify the construction of client web applications. It contributes to the movement of data in one direction: from actions to the dispatcher, then to the storage and at the end to the view. This approach allows you to separate state from the rest of the application and encourages synchronous updates. You can learn more about Flux from the official documentation at facebook.github.io/flux/docs/overview.html.

    Vuex works on the same principle, helping to change state predictably and synchronously. Developers do not need to worry about the consequences of updating the state with synchronous and asynchronous functions. Imagine that we are interacting with a server API that returns data in JSON format. What happens if at the same time this data is modified by a third-party library? We do not want an unpredictable result. Vuex helps to avoid such situations by eliminating any asynchronous changes.
    You are probably wondering why we even need the Vuex library. In the end, Vue.js allows you to pass information to components. As you know from previous chapters, input parameters and user events are intended for this. We could even create our own event bus for data transfer and inter-component communication. An example of such a mechanism is presented in Fig. 10.1.

    This would be appropriate for small applications with a handful of components. In this case, you need to transfer information to only a few recipients. But what if the application is larger, more complex, and layered? It’s clear that in a large project it’s not so easy to keep track of all the callback functions, input parameters and events.

    Just for such situations, the Vuex library was created. It allows you to organize work with the state in the form of a centralized repository. Imagine a scenario worth thinking about using Vuex. For example, we are working on a blog with articles and comments that you can create, edit and delete. At the same time, we have an administration panel that allows you to block and add users.

    Let's see how this is implemented using Vuex. In fig. Figure 10.2 shows that the EditBio component is a child of the admin panel. He needs access to user information so that he can update it. Working with Vuex, we can access the central repository, modify the data and save the changes directly from the EditBio component. This is much better than passing information from the root instance of Vue.js to the Admin component and then to EditBio using the input parameters. It would be difficult for us to keep track of data located in different places.

    image

    image

    Nevertheless, the use of Vuex comes at a price in the form of additional boilerplate code and complexity of the application structure. As already mentioned, it is better not to use this library in simple projects consisting of several components. Its real potential is manifested in large applications with a more complex state.

    10.2. State and mutation in Vuex


    Vuex stores the state of the entire application in a single object, which is also called a single source of truth. As the name suggests, all data is collected in one place and are not duplicated in other parts of the code.
    TIP

    It is worth noting that we are not required to store all of our data in Vuex. Individual components may have their own local state. In certain situations, this is preferable. For example, your component has a variable that is used only inside it. She must remain local.
    Consider a simple example of working with state in Vuex. All our code will be placed in one file. Later you will learn how to add Vuex to a project using the Vue-CLI. Open a text editor and create the vuex-state.html file. We will display a message located in the central repository, and a counter. The result is shown in fig. 10.3.

    imageFirst, add script tags with links to Vue and Vuex. Then create the HTML markup. We will use the tags H1, H2, H3 and button. The h1 tag displays the header with a local variable declared in the Vue.js. instance. The welcome and counter messages are executed as calculated properties based on the Vuex repository.

    The button element triggers an increment action. Copy the code from Listing 10.1 into the vuex-state.html file.

    image

    When you're done with HTML markup, let's get started on creating the Vuex repository. It will contain all the application data, including the msg and count properties.

    To update the state, we use mutations. This is something like setters from other programming languages. The setter sets the value, the mutation updates the state of the program. In Vuex, mutations must be synchronous. In our example, the counter is incremented only at the click of a button, so there is no need to worry about asynchronous code (later we will look at actions that help solve problems with asynchrony).

    Create an increment function inside the mutations object that increments the state. Take the code in Listing 10.2 and paste it at the bottom of the vuex-state.html file.

    image

    So, we have prepared HTML markup and Vuex repository. Now you can add logic that will connect them. We want the template to display the msg and counter values, which are part of the Vuex state. In addition, counter needs to be updated.

    Create an instance of Vue.js with a new data function that will return a local header property with the text of Vuex App. In the computed section, add the calculated properties welcome and counter. The first will return store.state.msg, and the second - store.state.count.

    Finally, you need to add a method called increment. A mutation has been declared in the Vuex repository, but we cannot use it directly to update the state. A special commit function is provided for this. It tells Vuex to update the repository and thereby saves the changes. The expression store.commit ('increment') performs the mutation. Insert the following snippet (Listing 10.3) immediately after the code created in Listing 10.2.

    image

    This is a full-fledged working application based on Vuex! Try to press the button - each time you press the counter, it should increase by 1.

    Update the code so that pressing the button increments the counter by 10. If you look closely at the increment mutation, you will notice that it takes only one argument, state. Let's pass another one - let's call it payload. It will be passed by the increment method, which is created in the root instance of Vue.js.

    Copy the contents of vuex-state.html to the new vuex-state-pass.html file. Using this application as an example, we show how arguments are passed.

    As you can see in Listing 10.4, only the mutations object and increment method need to be updated. Add to the mutation increment another argument called payload. This is the value by which state.count will increase. Find the call to store.commit inside the increment method and specify 10 as the second argument. Update the vuex-state.html file as shown below.

    image

    Save the vuex-state-pass.html file and open it in a browser. Now, when the button is pressed, the counter should increase by 10, and not 1. If something went wrong, check the browser console and make sure that you did not make any typos.

    10.3. Getters and Actions


    In the previous example, we accessed the store directly from the calculated properties. But what if we had several components that needed the same access? Suppose we want to display a welcome message in capital letters. In this case, getters will help us.

    Getters are part of Vuex. They allow you to implement unified access to state in all components. Let's take an example from section 10.2 and instead of direct access to the repository through calculated properties, we use getters. In addition, we make the getter for the msg property translate all its letters to uppercase.

    Copy the contents of the vuex-state-pass.html file to vuex-state-getter-action.html. To simplify the task, leave the HTML code unchanged. In the end, you should get something similar to fig. 10.4.

    imageAs you can see, the Hello World message is now displayed in words. Pressing the Press Me button increments the counter in the same way as in the previous example.

    Locate the Vuex.Store construct inside the new vuex-state-getter-action.html file just below the imageAdd after mutations tag with a new object called getters. Create the msg and count methods inside this object, as shown in Listing 10.5. Both methods accept the same state argument.

    The msg getter will return state.msg.toUppercase (). Thanks to this, the message is always displayed in uppercase. In the get getter, we will return state.count. After adding getters below the mutations, the vuex-state-getter-action.html file should look like this.

    image

    Actions are another integral part of Vuex. I mentioned earlier that mutations must be synchronous. But what if we work with asynchronous code? How to make asynchronous calls able to change state? Vuex actions will help us with this.
    Imagine that the application is accessing the server and waiting for a response. This is an example of an asynchronous action. Unfortunately, mutations are asynchronous, so we cannot use them here. Instead, add an asynchronous operation based on the Vuex action.

    To create a delay, use the setTimeout function. Open the vuex-state-getter-action.html file and add the actions object to it immediately after getters. Inside this object, place the increment action, which takes the context and payload arguments. Using context, we will save the changes. Place the context.commit operation inside setTimeout. This way we simulate a delay on the server. We can also pass payload argument to context.commit, which then gets into the mutation. Update the code based on Listing 10.6.

    After updating Vuex.Store, you can proceed to the root instance of Vue.js. The computed property will not access the store directly, as before, but using getters. We are also modifying the increment method. To access the new Vuex property that we created earlier, it will use the call store.dispatch ('increment', 10).

    image

    The first argument to the dispatch call is the name of the action, and the second argument always contains additional data that is passed to this action.
    TIP

    Additional data can be a regular variable or even an object.
    Update the Vue.js instance in the vuex-state-getter-action.html file as shown in Listing 10.7.
    image

    Download the application and press the button several times. You should notice a delay, but the counter will increase by 10 after each press.

    10.4. Using Vuex in a pet store project using Vue-CLI


    Let's get back to the pet store project we were working on. If you have not forgotten, we settled on adding animations and transitions. Now we integrate the Vuex library that we met earlier.

    We transfer the data on the goods to the warehouse. As you remember from the previous chapters, the data was initialized in the created hook inside the Main component. Now this hook should generate a new event that initializes the Vuex repository. We will also add the calculated property products, which retrieves products using the getter (we will create it later). The final result will look like in Fig. 10.5.

    image

    10.4.1. Install Vuex with Vue-CLI


    First, install Vuex! This is a simple process. Prepare the latest version of the pet store that we worked on in chapter 8. You can also download all the code for this chapter on GitHub at github.com/ErikCH/VuejsInActionCode.

    Open a terminal window and go to the root directory of the project. To install the latest version of Vuex, run the following command:

    $ npm install vuex

    and save the record about it in the package.json file of the pet store.

    Now you need to add the storage to the main.js file, which is located in the src folder. The repository itself does not exist yet, but we import it anyway. Usually it is in the src / store / store.js file, but you can choose a different path - all developers have their own preferences. Let us dwell on the generally accepted option. Later in this chapter we will discuss an alternative directory structure using modules.

    You need to add storage to the root instance of Vue.js below the router, as shown in Listing 10.8. By the way, we use the ES6 standard, so store: store can be shortened to store.

    image

    After connecting the storage to the root instance, we can access it from any part of the application. Create the src / store / store.js file. In it we will place the Vuex repository with information about the products offered by the pet store. At the top, add two import statements, one for Vue and Vuex. Then specify Vue.use (Vuex) to put everything together.

    We imported the repository in the main.js file from ./store/store. Now you need to export the store object inside store.js. As you can see in Listing 10.9, we export a const store value equal to Vuex.Store.

    First, add objects with state and mutations. The state will contain an empty object called products. Soon we will fill it with the initStore method. The mutation is called SET_STORE, it will assign the transferred goods to the state.products property. Paste the code from the following listing into the src / store / store.js file we just created.

    image

    We need to create an action and getter in the repository. A getter will return a products object. The action is a little more complicated. You should transfer the created hook, which reads the static / products.json file using Axios, to the actions object inside Vuex.

    I mentioned earlier that mutations must be synchronous and that only actions inside Vuex can accept asynchronous code. To get around this limitation, put the Axios code in the Vuex action.

    Create an actions object in the store.js file and add the initStore method to it. Copy the contents of the created hook from the components / Main.vue file into this method. Instead of assigning response.data.products to the products object, we invoke the mutation using the commit function. Pass response.data.products as an argument to SET_STORE. The resulting code should look like this (Listing 10.10).

    image

    We are almost done, we just have to update the Main.vue file and transfer the goods from the local products object to the Vuex repository. Open the src / components / Main.vue file and find the data function. Remove the line products: {}. We will access the goods from the computed property that the store returns.

    Find the calculated properties cartItemCount and sortedProducts inside Main.vue, they should go right after the methods section. Add the products property and make it return the getter of the same name.

    We connected the repository to the root instance of Vue.js in the main.js file, so we no longer need to import it. In addition, when using Vue-CLI, storage is always available in the form of this. $ Store. Do not forget the $ sign, otherwise it will result in an error. Add the computed products property, as shown in Listing 10.11.

    image

    Find the created hook in which the products object is initialized, and delete its contents. Instead, insert the initStore action call that we created earlier in the Vuex repository. To call an action, use the dispatch function, as in the previous example. Listing 10.12 shows what the created hook should look like after updating the Main.vue file.

    image

    That should be enough. Run the npm run dev command in the terminal, and a window with the pet store application should appear on the screen. Try putting the goods in the basket and make sure that everything works as it should. If something went wrong, look for errors in the console. In the src / store / store.js file, instead of Vuex.store, you can accidentally type Vuex.Store. Remember this!

    10.5. Helper Methods Vuex


    Vuex provides convenient helper methods that make code more concise and get rid of adding the same getters, setters, mutations, and actions. For a complete list of Vuex helper methods, see the official guide at vuex.vuejs.org/guide/core-concepts.html. Let's see how they work.

    The main helper method you should be aware of is called mapGetters. It is used to add all available getters to the computed section and does not require listing each of them. But before using it, you need to import it inside the component. Once again, back to the pet store and add the mapGetters method.

    Open the src / components / Main.vue file and find the script tag. Somewhere inside this tag, the Header component should be imported. Connect mapGetters immediately after this import, as shown in Listing 10.13.

    image

    Now you need to update the computed property. Find the products function in the computed section and insert the mapGetters object in its place.

    mapGetters is a unique object, for its proper operation it is necessary to use the spread operator from ES6 - it expands the expression in a situation when any arguments are expected (zero or more). You can read more about this syntax in the MDN documentation at developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_syntax .

    mapGetters will ensure that all getters are added as computed properties. This is a much simpler and more elegant way than writing a separate computed property for each getter. All getters are listed in the mapGetters array. Add this helper method to the Main.vue file (Listing 10.14).

    image

    After running the npm run dev command, the pet store should work as before. The mapGetters helper method doesn't look very useful so far, but the more getters we add, the more time it will save.

    There are three more helper methods you should be aware of: mapState, mapMutations, and mapActions. They all work in a similar way, reducing the amount of boilerplate code that you have to write manually.

    Imagine that your repository contains several pieces of data and access to the state is carried out directly from the component, without using any getters. In this case, you can use the mapState method inside the computed section (Listing 10.15).

    image

    Now imagine that you need to use several mutations in a component. To simplify this process, use the mapMutations helper method (Listing 10.16), as is done with mapState and mapGetters. Next mut1 binds this.mut1 () to this. $ Store.commit ('mut1').

    image

    Finally, consider the mapActions helper method. It allows you to add Vuex actions to the application, eliminating the need to create methods with dispatch calls in each case. Returning to the previous example, imagine that the application contains some asynchronous operations. Since the use of mutations is excluded, we must resort to action. After creating them in Vuex, you need to access them in the component's methods object. This problem can be solved using mapActions. act1 will bind this.act1 () to this. $ store.dispatch ('act1'), as shown in Listing 10.17.

    image

    Finally, consider the mapActions helper method. It allows you to add Vuex actions to the application, eliminating the need to create methods with dispatch calls in each case. Returning to the previous example, imagine that the application contains some asynchronous operations. Since the use of mutations is excluded, we must resort to action. After creating them in Vuex, you need to access them in the component's methods object. This problem can be solved using mapActions. act1 will bind this.act1 () to this. $ store.dispatch ('act1'), as shown in Listing 10.17.

    image

    As the application grows, these helper methods will be increasingly useful, reducing the amount of code that needs to be written. Keep in mind that you need to think through property names in your repository, as helper methods allow you to access them in components.

    10.6. Brief Introduction to Modules


    At the beginning of this chapter, we created the store.js file in the src / store directory. For a small project, this approach is quite appropriate. But what if we are dealing with a much larger application? The store.js file will grow rapidly and it will be difficult to keep track of everything that happens in it.
    To solve this problem, Vuex offers the concept of modules. Modules allow you to divide the storage into several smaller parts. Each module has its own states, mutations, actions and getters, you can even make them nested into each other.

    We rewrite the pet shop using modules. The store.js file will remain in place, but next to it you should create the modules folder and place the products.js file there. The directory structure should look like the one in fig. 10.6.

    imageInside products.js, you need to create four objects: state, getters, actions and mutations. The contents of each of them should be copied from the store.js file.

    Open the src / store / store.js file and start copying the code from it. When done, the products.js file should look like this (Listing 10.18).

    Now we need to export all the code that we added to the product.js file. This will import it into store.js. At the bottom of the file, add the expression export default. This is an export instruction in ES6 format that allows you to import this code from other files (Listing 10.19).

    The store.js file should be updated. Add a modules object to it, inside which you can list all the new modules. Remember to import the modules / products file that we created earlier.

    image

    Our example contains only one module, so immediately add it to the modules object. You also need to remove all unnecessary from Vuex.Store, as shown in Listing 10.20.

    image

    By importing the modules, we completed the refactoring process. After refreshing the page, the application should work exactly as before.
    Namespaces in Vuex

    In some large projects, uniting can cause certain problems. As new modules are added, conflicts may arise with the names of actions, getters, mutations, and state properties. For example, you might accidentally assign the same name to two getters in different files. And, since everything is in a global global namespace in Vuex, a duplicate key error will occur in the console.
    To avoid this problem, put each module in a separate namespace. To do this, just specify namespaced: true at the top of Vuex.store. Read more about this feature in the official Vuex documentation at vuex.vuejs.org/en/guide/modules.html.
    »More information about the book can be found on the publisher’s website
    » Contents
    » Excerpt

    For Khabrozhiteley 25% discount on the coupon - Vue.js

    Upon payment of the paper version of the book, an electronic version of the book is sent by e-mail.

    Also popular now: