Effector vs. Vuex. Which storage management is better for VueJS app?
(Spoiler: I believe it’s Effector, and here’s why)
Recently, I had to create a few applications using VueJS after extensive experience with building React apps. And for me, it felt like there was not much diversity in the toolbox that the VueJS eco-system provided in terms of managing the store, unlike React-based apps. Most of those tools were just blindly re-adopted from one project to another, without a clear understanding of the benefits particular solution.
In this article, I would like to show one powerful alternative data storage solution, which has the same set of features as Vuex does, but it is less restrictive and more advanced in reducing code complexity. I’m talking about Effector — a less popular, but somewhat more impressive framework-agnostic store manager.
For the sake of comparison, I‘ve created a small and simple scaffolding VueJS app using both solutions. The applications present a simple form and data store which collects this data in-store and doing some filters by one of the fields.
Main requirements for a store manager that I was looking at while doing the comparison were the following:
- Reactivity: the store must react to events/actions and be immutable which means that on every change it must return a new app state and propagate it to VueJS components.
- Testing: it should be easy to write unit tests for the store changes and updates of the templates. The same goes for functional testing of VueJS components and UI.
- Simplicity: the changing of the store should be predictable
- Optimal bundle size
Effector is a library of API methods above the store which can manipulate, modify, and update it in many different ways. One of its biggest advantages is the stand-alone core with powerful methods and implementations for most popular frameworks: Effector-react and Effector-vue. The main principle of its architecture is explained here.
The library has the following crucial entities: createStore and createStoreObject, that create multiple stores, and events (createEvent) aimed to change the store. Another cool feature is the opportunity to trigger events from any place of the application (I’ll show it in the example further in the article). Splitting main architecture from specific logic of frameworks like an instance of Vue is a very smart way compare to Vuex architecture which doesn’t provide the way to change the storage outside of Vue component. Due to framework-agnostic architecture, the Effector can be used regardless of the chosen framework.
Points of comparison
Initializing storage
Vuex provides a single store like monolith which is initialized in the main Vue instance of the app along with the router, internalization, etc. and we must keep all data in there. Here is an example:
This approach is not so convenient when the app grows big enough. In order to prevent unnecessary re-rendering of the template, one needs to create many “getters” to derive particular data from the store for filters, grouping, and mapping original data. Moreover, Vuex is quite verbose when it needs to have many actions mutating specific parts of the store.
Effector is not so strict in the ways of how to initialize the store but we can always follow best practices. Here I show how Vuex structure might be reorganized in Effector architecture just splitting the global store into multiple stores. That helps to handle the effects and changes of each state easily.
Next thing, when a different part of storage should be changed by a single action then in Vuex it may look a bit messy. We can see a lot of side effects as in the code below like searchText or filters. Here we have 3 stores: servicesList, filters and searchText, combined in one object, and all of them should be subscribed to one event:
The Action resetAllFilters in the example above modifies different parts of the store and creates side effects. But it would be better to split up “servicesList” into a few different stores which are subscribed to the same event (resetAllFilters).
Doing this in Effector would look smth like this:
Notice that we can use multiple stores separately or combine all stores in one and use all of them if it’s needed. All of these stores are reactive and will immediately respond to events.
Another important feature of Effector store is the endless options of combining (combine) different stores together to reduce and filter particular data that you want to put in a template. Ways of how to combine stores together and in which way it should be done may differ depending on the custom logic of your app and your personal preferences of course. There are no restrictions dictated by the library. Thus, it’s very handy to mix and subscribe to particular stores and combine them together. Here’s an example:
Usage of storage in the component and template
Vuex creates $store in the context of each Vue component. Then, the variable $store is being inherited in all children components.
Effector store in Vue (effector-vue provides access to the store inside Vue component) encapsulates store within one component where it’s defined. Components that use effector-vue don’t affect each bother. So, part of storage used in one component won’t be inherited a chain of its children.
As a result, I would suggest not to mix Vuex and Effector/Effector-vue in one app because it will cause a collision in namespaces. Effector-vue also adds $store variable inside the component where it’s used and it will collide with $store from Vuex, and this variable will contain part of methods from Vuex and effector-vue at the same time.
There are two ways to give access to the store inside Vue component when using Effector. One is using the “createComponent” wrapper, and another one is just a usual way of adding a new method “effector” to the component class.
Let’s look at a concrete example from my test app:
- Using “effector” class method
It creates “state” variable in the template. We have access to this using this state variable and this variable will be undefined for parents and children relative to this component.
2. Using “createComponent” from Vue-effector is here
export default createComponent({…}, store)
Unit tests
Creating tests using vue/test-utils library is quite easy and convenient, except for cases when we, for example, need to test changes in the store made by the submission of some huge form.
That’s how we can test the case when new data is being added to the Vuex store:
You can see that we need to trigger “submit” method of the form to push data into the store, but for that, it’s necessary to work directly with UI components and DOM elements which is always painful.
Unlike Vuex, Effector provides a declarative way to create unit-tests. We can change the storage outside of the Vue component thanks to global methods that allow us to retrieve the storage and trigger events:
Takeaways
- If you want to build a simple application without extensive data and complicated optimization then there is no difference in terms of efforts between choosing the particular solution.
- Vue-effector is more powerful in terms of the reactivity of data. And the same implementation for React is even more powerful due to the support of cool features like hooks.
- Documentation for effector requires some prior knowledge about data structures, graphs, and algorithms, but the docs are being continuously improved by the community.
Major differences between Vuex and Effector
I hope my examples and conclusions will help you to extend the range of efficient tools for your Vue applications.
References
Repo to the project — https://github.com/pulpiks/vuex-effector
Docs — https://effector.now.sh
Repo — https://github.com/zerobias/effector
Twitter — https://twitter.com/effectorjs
Besides that, all discussions go very actively in Telegram chats inside the English-speaking community (https://t.me/effector_en ) and Russian-based community (https://t.me/effector_ru).
Vuex docs — https://vuex.vuejs.org/guide/