Correct and Efficient Vuex Using. Part II
In the first part of the article, we looked at such components of Vuex as storage, state, getters, mutations, and actions. You can see all in details here https://amoniac.eu/blog/post/correct-and-efficient-vuex-using-part-i
And we continue our review of the Vuex library and talk about modules, application structure, plugins, etc.
Modules
Due to the use of a single state tree, all global application data is placed in one large object. As the application grows, the storage can swell significantly. To help with this, Vuex lets you split storage into modules. Each module can contain its own state, mutations, actions, getters, and even built-in submodules, this structure is fractal.
The first argument that mutations and getters receive is the local state of the module. Similarly, context.state
in actions also indicates the local state of the module, and the root is available in context.rootState
. By default, actions, mutations, and getters inside modules are registered in the global namespace. This allows several modules to respond to the same type of mutations/actions.
If you want to make the modules more self-sufficient and ready for reuse, you can create it with your namespace by specifying the namespaced: true
option. When a module is registered, all its getters, actions, and mutations will be automatically associated with this namespace, based on the path along which the module is registered.
Getters and actions with their namespace will receive their local getters
, dispatch
, and commit
. In other words, you can use the contents of a module without writing prefixes in the same module. Switching between namespaces does not affect the code inside the module.
If you want to use the global state and getters, rootState
and rootGetters
are passed in the 3rd, and 4th arguments to the getter function, as well as the properties in the context
object, passed to the action function. To trigger actions or commit mutations in the global namespace, add {root: true}
with the 3rd argument to dispatch
and commit
.
If you want to register global actions in namespaced modules, you can mark it with root: true
and place the action definition to function handler
. Furthermore, you can create namespaced helpers by using createNamespacedHelpers
. It returns an object having new component binding helpers that are bound with the given namespace value.
You may be concerned about the unpredictability of the namespace for your modules when you create a plug-in with its modules and the ability for users to add them to the Vuex repository. Your modules will also be placed in the namespace if plugin users add your modules to the module with their namespace. To adapt to this situation, you may need to get the namespace value through the plugin settings.
You can register the module even after the storage has been created using the store.registerModule
method. Module status will be available as store.state.myModule
and store.state.nested.myModule
. Dynamic module registration allows other Vue plugins also to use Vuex to manage their state by adding a module to the application data store. For example, the vuex-router-sync
library integrates vue-router into vuex, reflecting a change in the current application path in a dynamically attached module.
You can delete a dynamically registered module using the store.unregisterModule (moduleName)
. Please note that the static, defined at the time the repository was created, modules cannot be removed using this method.
Sometimes we may need to create several instances of the module, for example:
- creation of several storages that are used by one module, for example, to avoid stateful singletones in the SSR when using the
runInNewContext
option iffalse
or'once'
; - registering the module several times in one repository.
If we use an object to determine the state of a module, then this state object will be used by reference and cause pollution of the state of the storage/module during its mutations. This is actually the same problem with data
inside Vue components. So the solution will be the same.
Application structure
In reality, Vuex does not impose any significant restrictions on the code structure used. However, it requires compliance with several high-level principles:
- The global state of the application must be contained in global storage;
- The only mechanism for changing this state are mutations that are synchronous transactions;
- Asynchronous operations are encapsulated in actions or their combinations.
As long as you follow these rules, you can use any project structure. If your storage file gets too large, just start putting actions, mutations, and getters into separate files. For any non-trivial application, you will most likely need to use modules. Here is an example of a possible project structure. For reference, you can use the shopping cart example.
Plugins
Vuex repositories accept the plugins
option, which provides hooks for each mutation. The Vuex plugin is just a function that receives storage as a single parameter. Plugins are not allowed to directly change the state of the application as well as components. They can only cause changes indirectly using mutations.
By causing mutations, the plugin can synchronize the data source with the data store in the application. For example, to synchronize storage with a web socket, the example is intentionally simplified, in a real situation, createWebSocketPlugin
would have additional options. Sometimes a plugin may need to “take a nugget” of the application state or compare the “before” and “after” mutations. To do this, use deep copying of the state object.
Impression plugins should only be used during development. When using webpack or Browserify, we can give this moment at their mercy. The plugin will be used by default. In the production environment, you will need DefinePlugin for webpack, or envify for Browserify to change the value of process.env.NODE_ENV! == 'production'
to false
in the final assembly.
Vuex comes with a logging plugin that can be used for debugging. You can also enable the logging plugin directly using a separate <script>
tag, which places the createVuexLogger
function in the global namespace. Please note that this plugin makes state casts, so you should use it only at the development stage.
Strict mode
To enable strict mode, specify strict: true
when creating the Vuex repository. In strict mode, any attempt to make changes to the Vuex state, except mutations, will throw an error. This ensures that all state mutations are explicitly tracked through debugging tools.
Do not use strict mode in production! Strict mode triggers deep tracking of the application state tree in synchronous mode to detect inappropriate mutations, and this can be costly for performance when a large number of mutations occur. Be sure to turn this mode off in production to avoid performance degradation.
Work with forms
When using strict mode, Vuex may not seem obvious how to use the v-model
with the Vuex state part. Suppose obj
is a computed property that returns an object reference from the repository. In this case, the v-model
will try to change the obj.message
value during user actions directly. In strict mode, such changes will trigger an error because they occur outside of Vuex mutation handlers. To work with Vuex in this situation, you should bind the value to <input>
and track its changes by the input
or change
event.
Testing
The main subject of unit testing in Vuex are mutations and actions. Mutations are fairly easy to test, as they are just simple functions whose behavior depends entirely on the parameters passed. One trick is that if you use ES2015 modules and put your mutations in the store.js
file, then in addition to the default export, you must export the mutations using named exports.
Testing activities are a bit more complicated, as they can access external APIs. When testing actions, you usually have to fake external objects - for example, calls to the API can be moved to a separate service, and as part of the tests, this service can be replaced with a fake one. To simplify the simulation of dependencies, you can use webpack and inject-loader to build test files.
Getters doing complex calculations would also be helpful to test. As with mutations, everything is simple. If you correctly follow the rules for writing mutations and actions, the resulting tests should not depend on the browser API. Therefore, they can be assembled by webpack and run in Node. On the other hand, you can use mocha-loader
or Karma + karma-webpack
, and run tests in real browsers.
Hot reboot
Vuex supports hot-swapping of mutations, modules, actions, and getters at development time using the webpack Hot Module Replacement API. Similar functionality in Browserify is achievable using the browserify-hmr plugin. For mutations and modules, you need to use the store.hotUpdate()
API method.
Why Vuex Actions are the Ideal API Interface
If you are working on a project in which the back end and front end are developing at the same time, or you are working in a UI/Frontend team that can even create a user interface before the back end exists, you are probably familiar with the problem when you need to drown out back end parts or data as the front end develops.
The general way that this manifests is with purely static templates or content, with placeholders and text right in your interface templates. A step away from this is some form of fixtures, data that is statically loaded by the interface and injected into place. Both of them often face the same set of problems. When the back end is finally available, there is a bunch of refactoring to get the data in place.
Even if the data structure from the back end matches your fixtures, you still have to cross to find each integration point. And if the structure is different, you should not only do this, but you must figure out how you can either change the external interface or create an abstraction layer that transforms the data.
Strengths and Benefits of Vuex Storage
Compared to a simple global object, the Vuex repository has many significant advantages and benefits:
- Vuex Storage - Reactive. As soon as components get the state from it, they will reactively update their views every time the state changes.
- Components cannot directly change the state of the repository. The only way to change the state of the repository is to commit the mutations explicitly. This ensures that every state change leaves a tracked record, which makes debugging and testing the application easier.
- Components cannot directly change the state of the repository. The only way to change the state of the repository is to commit the mutations explicitly. This ensures that every state change leaves a tracked record, which makes debugging and testing the application easier.
- You can easily debug your application by integrating Vuex with the Vue DevTools extension.
- Vuex repository gives you a general picture of the state of how everything is connected and affects the application.
- It is easier to maintain and synchronize the state between several components, even if the hierarchy of elements is changing.
- Vuex enables the direct interaction of components with each other.
- If the component is destroyed, the state in the Vuex repository will remain untouched.
Summary
When working with Vuex, we need to remember some crucial points. Vuex creates a repository that consists of states, getters, mutations, and actions. To update or change state, must make a mutation. To perform an asynchronous task, you need an action. Actions, if successful, commit a mutation that changes state, thereby updating the presentation. The application state is stored as one large JSON object. Getters are used to access values in the store. Mutations update condition. It should be remembered that mutations are synchronous. All asynchronous operations must be performed within actions. Actions change state, initiating mutations. Make it a rule to initiate mutations solely through action. Modules can be used to organize storage in several small files.
Vuex makes working with Vue much more comfortable and more fun. If you are new, there may be situations where it is difficult to decide whether to use Vuex in certain areas of application. Follow instincts and reach high speed pretty quickly.