📜 ⬆️ ⬇️

5 tricks to help develop on vue.js + vuex

Recently decided to deal with vue.js. The best way to learn technology is to write something on it. To this end, my old route planner was rewritten, and this was the project . The code is large enough to face the scaling task.

In this article I will give a number of techniques that, in my opinion, will help in the development of any large project. This material is for you if you have already written your todo list on vue.js + vuex, but have not yet dug into large-scale cycling.



1. Centralized Event Bus


Any project on vue.js consists of nested components. The basic principle is props down, events up. The subcomponent receives data from the parent that it cannot change, and a list of parent events that it can run.
')
The principle is valid, but it creates a strong connection. If the target component is deeply nested, you have to drag data and events through all the wrappers.

We will deal with the events. It is often useful to have a global event emitter with which any component can communicate, regardless of the hierarchy. It is very easy to make; no additional libraries are needed:

Object.defineProperty(Vue.prototype,"$bus",{ get: function() { return this.$root.bus; } }); new Vue({ el: '#app', data: { bus: new Vue({}) // Here we bind our event bus to our $root Vue model. } }); 

After that, in any component there is access to this. $ Bus, you can subscribe to events through this. $ Bus. $ On () and call them through this. $ Bus. $ Emit (). Here is an example .

It is very important to understand that this. $ Bus is a global object for the entire application. If you forget to unsubscribe, the components remain in the memory of this object. Therefore, for each this. $ Bus. $ On in mounted there must be a corresponding this. $ Bus. $ Off in beforeDestroy. For example:

 mounted: function() { this._someEvent = (..) => { .. } this._otherEvent = (..) => { .. } this.$bus.$on("someEvent",this._someEvent); this.$bus.$on("otherEvent",this._otherEvent); }, beforeDestroy: function() { this._someEvent && this.$bus.$off("someEvent",this._someEvent); this._otherEvent && this.$bus.$off("otherEvent",this._otherEvent); } 

2. Centralized Promises Bus


Sometimes in a component you need to initialize some asynchronous piece (for example, an instance of google maps) that you want to access from other components. To do this, you can organize an object that will store promises. For example, such . As in the case of the event bus, do not forget to be deleted when the component is deinitialized. And in general, the above method can attach any external object with any logic to vue.

3. Flat structures (flatten store)


In a complex project, data is often heavily embedded. Working with such data is inconvenient in both vuex and redux. It is recommended to reduce nesting, for example, using the utility normalizr . The utility is good, but it is even better to understand what it does. I did not immediately come to understand the flat structure, for the same type of myself I will consider a detailed example.

We have projects, in each - an array of layers, in each layer - an array of pages: projects> layers> pages. How to organize storage?

The first thing that comes to mind is the usual nested structure:

 projects: [{ id: 1, layers: [{ id: 1, pages: [{ id: 1, name: "page1" },{ id: 2, name: "page2" }] }] }]; 

This structure is easy to read, it is easy to run foreach on projects, render subcomponents with lists of layers, and so on. But suppose you need to change the name of the page with id: 1. Inside some small component that renders the page, call $ store.dispatch (“changePageName”, {id: 1, name: “new name”}). How to find a place where in this deeply nested structure lies the desired page with id: 1? Run across the repository? Not the best solution.

You can specify the full path, such as

 $store.dispatch("changePageName",{projectId:1,layerId:1,id:1,name:"new name"}) 

But this means that in every small component of the page rendering, you need to drag the entire hierarchy, and the projectId, and layerId. Inconvenient.

Second attempt, from sql:

 projects: [{id:1}], layers: [{id:1,projectId:1}], pages: [{ id: 1, name: "page1", layerId: 1, projectId: 1 },{ id: 2, name: "page2", layerId: 1, projectId: 1 }] 

Now the data is easy to change. But hard to run. To display all the pages in one layer, you need to run through all the pages in general. This can be hidden in getter, or in template rendering, but there will still be a run.

Third attempt, normalizr approach:

 projects: [{ id: 1, layersIds: [1] }], layers: { 1: { pagesIds: [1,2] } }, pages: { 1: {name:"page1"}, 2: {name:"page2"} } 

Now all the pages of the layer can be obtained through the trivial getter

 layerPages: (state,getters) => (layerId) => { const layer = state.layers[layerId]; if (!layer || !layer.pagesIds || layer.pagesIds.length==0) return []; return layer.pagesIds.map(pageId => state.pages[pageId]); } 

Note that the getter does not run through the list of all pages. Data is easy to change. The order of the pages in the layer is specified in the layer object, and this is also correct, since the re-sorting procedure is usually located in the component that lists the objects, in our case it is the component that renders the layer.

4. Mutations are not needed.


According to the rules of vuex, changes to the repository data should occur only in functions-mutations, mutations should be synchronous. In vuex is the main logic of the application. Therefore, the data validation unit will also be logical to include in the repository.

But validation is not always synchronous. Therefore, at least part of the validation logic will not be in mutations, but in actions.

I suggest not to break the logic, and to store in actions in general the whole validation. Mutations become primitive, consist of elementary assignments. But then they can not be accessed directly from the application. Those. mutations are a kind of utilitarian thing inside the repository, which is useful except for the vuex debugger. The application communicates with the repository only through actions. In my application, any action, even synchronous, always returns a promise. It seems to me that knowing that all actions are asynchronous (and working with them as with promises) is easier than remembering what is which.

5. Restriction of reactivity


Sometimes it happens that the data in the repository does not change. For example, it may be the search results of objects on the map, requested from the external api. Each result is a complex object with many fields and methods. It is necessary to display a list of results. Need reactivity list. But the data inside the objects themselves are constant, and there is no need to track the change of each property. To limit reactivity, you can use Object.freeze.

But I prefer a more stupid method: let state store only a list of id-snik, and the results themselves are next to each other in the array. Type:

 const results = {}; const state = {resultIds:[]}; const getters = { results: function(state) { return _.map(state.resultsIds,id => results[id]); } } const mutations = { updateResults: function(state,data) { const new = {}; const newIds = []; data.forEach(r => { new[r.id] = r; newIds.push(r.id); }); results = new; state.resultsIds = newIds; } } 

Questions


Something I did not get as beautiful as I wanted. Here are my questions to the community:

- How to beat css animation harder to change the opacity? Often you want to animate the appearance of a block of unknown size, i.e. change its height from height: 0 to height: auto.
This is easily solved with javascript - just wrapped in a container with overflow: hidden, we look at the height of the wrapped element and animate the height of the container. Can this be solved via css?

- I am looking for a normal way of working with icons in a webpack, so far without success (therefore, I continue to use fontello). Like whhg icons. Pulled svg, split into files. I want to select multiple files and automatically collect inline font + classes based on file names. What can you do?

Source: https://habr.com/ru/post/332628/


All Articles