preface

This article summarizes most of the concepts involved in the actual development of VUEX, adds many tips from my own experience in the actual development project, and finally adds my own vuex construction project. In short, a lot of text, code is also a lot of, I suggest you collect (funny face ~) and then combined with the official document slowly look.

How to eat: Knock while looking! Beginners are advised to look at “1, Core Concepts” and then build locally according to “5, Building locally on actual Projects”. Or you can be lazy and verify it directly on the official example. If you’re having a problem with actual development, follow the directory for tips that might solve your problem. Anyhow namely collect first! ((^ del ^))

1. Core concepts

1.1 State: Used to store data. It is in storeUnique data source, similar to data objects in VUE.

  • Single state tree: Contains all application-level states in a single object. Each app contains only one Store instance.
  • Evaluate attributes: Since Vuex’s state store is reactive, the easiest way to read state from a Store instance is to return some state (such as token) in a evaluated attribute.
  • Usage:
/ / define
new Vuex.Store({
    state: {
        bilibili: {
				acFun:"I want to live another 500 years."}}/ /...
})
// from the component
this.$store.state.bilibili.acFun
Copy the code

Tips: If a state is used as a public state for multiple components and does not want to be modified to contaminate other components. In this case, state can be written as return, similar to data in VUE (2.30+ only supported).

    state(){
		return{
			bilibili: {
				acFun:"I want to live another 500 years."}}}Copy the code

1.2 Module: Store is divided into different modules for convenient management and maintenance

  • Store can be divided into the module (module). Each module has its own state, mutation, action, getter, even nesting child module, from top to bottom in the same way.
  • Usage:
/ / define
const moduleA = {
    state: {... },mutations: {... },actions: {... },getters: {... }}const moduleB = {
    state: {... },mutations: {... },actions: {... }}const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    }
})

// used in the component
store.state.a // -> moduleA status
store.state.b // -> moduleB status
Copy the code

1.3 Getter: Filters and obtains shared data

  • When you need to process data in the Store or when it is reused by multiple components, you can use Getters, also known as computed properties in Vue, which is really a repackaging of state-based data.
  • The return value of the getter is cached based on its dependencies.
  • Usage:
// the first argument is the state of the module; The second argument getters is the store getters. Note that getters is not separated by module; The third parameter rootState is, as the name implies, the rootState
getters: {
    cartProducts(state, getters, rootState) 
        => (getters.allProducts.filter(p= > p.quantity)),

	dateFormat(state, getters) {
            let date = state.nowDate;
            return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} / ${date.getHours()}:${date.getMinutes()}`; }}// from the component
this.$store.getters.cartProducts

// Add: since getters will cache the results. If you don't want to cache, or if you want to pass a parameter to getters, you need to write the getter in functional form.
getters:{
/ /...
	test:(state) = >(param) = >{
		return state.todos.find(todo= >todo.id===id)
	}
}

Copy the code

1.4 Mutation: The only way to change state

  • Each mutation has a string event type (type) and a callback function
  • Mutation must be a synchronization function
  • Mutation cannot contain asynchronous operations
  • Since the state in store is reactive,mutation should also satisfy some considerations of vUE responsiveness when modifying:
    • It is best to initialize all required properties in your store in advance.
    • When you need to add new properties to an object, you should
      • Use vue.set (obj, 'newProp', 123), or
      • Replace an old object with a new one. For example, using the stage-3 object expansion operator we can write:
state.obj = { ... state.obj,newProp: 123 }
Copy the code
  • Mutation is the only way to modify state, and Mutations cannot be called directly, but store.mit will be called using the corresponding type.
  • Usage:
// Define the first argument state as the state of the module; The second argument, products, is the passing argument when the call is made
mutations: {
    setProducts (state, products) {
        state.allProducts = products
    }
}

// There are three types of submissions in a component. The first two types are payloads and the third type is an object

// The first type simply adds the argument to be passed
this.$store.commit('setProducts'.'GodOfWar4')

// The second method simply adds the argument to be passed
this.$store.commit('setProducts', {
	name:'GodOfWar4'.comment:"I fucking shoot!"
	})
// the value of setProducts is changed to the following form
setProducts (state, products) {
        state.allProducts = products.name // Write it this way
    }

// The third submits the state category as an attribute of the object, along with the parameters
this.$store.commit({
	type:'setProducts'.name:'GodOfWar4'.comment:"I fucking shoot!"
	})
// mutation is written in the same way as the second case.

Copy the code

1.5 Action: Mutation can be submitted using an asynchronous operation

  • Actions commit mutations rather than direct state changes. Actions can contain asynchronous operations
  • Action triggered by store.dispatch (asynchronous)
  • The action returns a promise
  • Mutations only changes the state data, so use actions to request the data, and it is recommended that the data be processed in actions, or put in the getter for data processing.
  • Usage:
      state: {
         count: 0
             },
      mutations: {                
         increment (state) {
          state.count++
         }
          },
      actions: {         // just submit the method inside 'commit' and 'mutations'.
         increment (context,payload) {
          context.commit('increment')}}// This is what we usually do by deconstructing abbreviations
  actions: {
   increment ({ commit },payload) {
         commit('increment')}}// used in components, same mutation, but changed from commit to dispatch
this.$store.dispatch('increment', {/ /.. payload})


// The first parameter, context, is a store object. You can write it as a store object. The second parameter, payload, is our pass parameter.
// So what does context contain? Through the source code can be clearly seen:

let res = handler({ 
   dispatch,
   commit,
   getters: store.getters,
   state: getNestedState(store.state, path),
   rootState: store.state
  }, payload, cb)

/ / it can be seen that the context includes five attributes: dispatch, commit, getters, state, rootState.
So we can destruct {commit,dispatch,state} to get only the attributes we need.
Copy the code
  • Group Action (exactly copy the instructions on the official website)

Actions are usually asynchronous, so how do you know when an Action ends? More importantly, how can we combine multiple actions to handle more complex asynchronous processes?

First, you need to understand that store.dispatch can handle the Promise returned by the handler of the triggered action, and that store.dispatch still returns promises:

actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

Copy the code

Now you can:

store.dispatch('actionA').then(() => {
  // ...
})

Copy the code

This is also possible in another action:

actions: {
  // ...
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')}}}Copy the code

Finally, if we use async/await, we can combine actions as follows:

GetData () and getOtherData() return Promise actions: {async actionA ({commit}) {commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA'// Wait for actionA to complete the commit('gotOtherData', await getOtherData())
  }
}

Copy the code

A single store.dispatch can trigger multiple action functions in different modules. In this case, the returned Promise will not be executed until all triggering functions have completed.

Tips:

  • One thing to note is that after binding state in the Store to a computed property in the Vue component, changing state requires mutation or action, Assigning directly to a Vue component (this.myState = 'ABC') will not work.

  • In Vuex modularity, state is the only thing that adds a hierarchy based on the alias of the module at the time of composition, while getters, mutations, and actions are merged directly under store.

  • Since VUEX is a one-way data flow, V-Model in VUE is bidirectional binding. Therefore, when v-Model binds data to VUEX, you need to listen for real-time modification of data in VUEX.

2. Legend analysis

  • Folder structure:

  • Vuex Organizational structure:

  • Vuex operation:

  • Vuex data flow:

3, Vuex installation

This section references # Vue component communication deep into Vuex, thanks here!

  • 3.1 Installing Vuex in a Project:
npm install vuex --save
Copy the code
  • 3.2 Creating a directory in the SRC directorystore/index.js, the code is as follows:
import Vue from 'vue'
import Vuex from 'vuex'
// When changing state, print it on console for easy debugging
import createLogger from 'vuex/dist/logger'

Vue.use(Vuex)

constdebug = process.env.NODE_ENV ! = ='production'

const state = {}
const getters = {}
const mutataions = {}
const actions = {}

export default new Vuex.Store({
    state,
    getters,
    mutataions,
    actions,
    // In strict mode, the state is invalid
    strict: debug,
    plugins: debug ? [createLogger()] : []
})
Copy the code
  • 3.3 In the entry filemain.jsAdd:
// ...
import router from './router'
import store from './store'

new Vue({
    el: '#app',
    router,
    store,
    // ...
})
Copy the code

Vue-router and Vuex are both vue plug-ins that are introduced during component instantiation. All components under this instance can be queried by this.$router and this

4. Auxiliary function usage

  • Again, in Vuex modularity (written as module), state is the only thing that adds a hierarchy based on the alias of the module at the time of composition, while getters, mutations, and actions are all merged directly under store. So auxiliary functions also meet the above requirements

  • Note: for simplicity, all use… And all the auxiliary functions we use are written in the Device module!

4.1 mapState

Write in the computed case

// Introduce mapState first
import {mapState} from 'vuex'

export default {
//1, written in the computed casecomputed: { ... mapState({// It is important to note that since we are using module, we need to write device as the arrow function.
  test1: state= > state.device.test

//test1:'test' written as a string is equivalent to state=>state.test
//test1(state){
//	return state.device.test + this.message
//} If the component this is needed in the return value, write it as a function})}},// In this case, you can get state.device.test directly from this.test1

Copy the code

Write in the case of methods

The biggest difference is that you must write this.test1() to get state.device.test

// Introduce mapState first
import {mapState} from 'vuex'

export default {
//2, in the case of methods, it's basically the same thing as in computed. But note that map states cannot be written directly inside a function! It can only be written in methods like this.methods: { ... mapState({// There are three ways to write it
  test1: state= > state.device.test

//test1:'test'  
//test1(state){
//	return state.device.test + this.message
/ /}})}},// The biggest difference is that you must write this.test1() to get state.device.test

Copy the code

Note: it is common practice to print data in state wrapped in getter, so mapState is rarely encountered in projects. In fact, I feel that the writing of these two cases is not very good, are more troublesome. Let test1=this.$store.state.device.test let test1=this.$store.state.device.test let test1=this.

4.2 mapGetters

Write in the computed case

// First introduce mapGetters
import {mapGetters} from 'vuex'

export default {
//1, written in the computed casecomputed: { ... mapGetters({// It should be noted that, different from mapState, although module is used, device cannot be added. Here vuex combines all getters together, and there is no device for module division. So you can only write it as a string. Also note: if the getters property name in the device is the same as the getters property name in the root. The attribute name of the root getters is overridden.
  test1: 'test'})}},// In the same case, getters.test can be obtained directly from this.test1

Copy the code

Write in the case of methods

The biggest difference is that you must write this.test1() to get state.device.test

// First introduce mapGetters
import {mapGetters} from 'vuex'

export default {
//2, in the case of methods, it's basically the same thing as in computed. But note that mapGetters cannot be written directly inside a function! It can only be written in methods like this.methods: { ... mapGetters({// Only strings are supported here as well
  test1:'test'})}},// The biggest difference is that you must write this.test1() to get getters.test

Copy the code

Again: different from mapState, although module is used, device cannot be added. Here, VUEX combines all getters together, and there is no device for module division. So you can only write it as a string. Also note: if the getters property name in the device is the same as the getters property name in the root. The attribute name of the root getters is overridden. And it can only be {test1:’test’} or [‘test’] where this.test equals getters.test

4.3 mapMutations

MapMutations maps store.mit (‘mutation name ‘) instead of mutation function, and like mapGetters, all mutations are combined together, so it cannot be differentiated by module name, so it can only be differentiated by naming itself. Or use your own namespace.

// Start with mapMutations
import {mapMutations} from 'vuex'

export default {
// We need to write methods, which are basically the same as mapGetters. You can write it as an object or an arraymethods: { ... mapMutations({test1:'test'
  }), 
/ /... mapMutations([
Use this. Test (param) to map to this.test(param).
/ /]),
/ /},
}


Copy the code

4.4 mapActions

MapActions maps store.dispatch(‘action name ‘), not action functions, and, like mapGetters, groups all actions together, so they cannot be distinguished by module names, only by naming. Or use your own namespace.

// Start with mapActions
import {mapActions} from 'vuex'

export default {
// We need to write methods, which are basically the same as mapGetters. You can write it as an object or an arraymethods: { ... mapActions({test1:'test'
  }), 
/ /... mapActions([
// this. Test (param) maps to this.$store. Dispatch ('test',param)
/ /]),
/ /},
}


Copy the code

5. Actual project construction

5.1. File structure construction

First of all,vuex’s actual project construction involves many people dividing modules by function. Such as:

Store ├ ─ ─ index. Js# where to export store├ ─ ─ state. Js# rootlevel state├ ─ ─ getters. Js# Secondary packaging state data├ ─ ─ actions. JsRoot level action├ ─ ─ mutations. JsMutation at the root level
Copy the code

However, I prefer to divide modules by business logic, after all, SRC and VUE of our build projects are also divided by business logic. So the actual project uses modules for module partitioning. For details, please refer to the vUE structure in the figure above. The folder structure is as follows:

Store ├ ─ ─ index. Js# where to export store├ ─ ─ modules# modules├ ─ ─ home. JsModule file for # home├ ─ ─ device. JsModule file for # device├ ─ ─ event. Js# event module file├ ─ ─ order. Js# order module file├ ─ ─ user. Js# user module file├ ─ ─ the js# log module file.Copy the code

5.2,index.js file configuration

import Vue from 'vue'  / / into the vue
import Vuex from 'vuex' / / introduce vuex
// Import your module
import home from './modules/home'
import device from './modules/device'
import event from './modules/event'
import order from './modules/order'
import user from './modules/user'
import log from './modules/log'

Vue.use(Vuex)
export default new Vuex.Store({
// Public information can be stored and processed here, such as token, user information, initialization information, etc.
  state: {
    token: '123'.userInfo: {
      userId: 0.userName: ' '.roleId: 0.roleName: ' '
  	},
    initialInfo: {
      menuList: [].functionList: [].projectNodeList: []}},mutations: {
    setToken (state, token) {
      state.token = token
  	},
    setUserInfo (state, userInfo) {
      state.userInfo = userInfo
  	} 
  },
 // Fill in the module we introduced here
  modules: {
	home,
    device,
	event,
	order,
	user,
	log
  }
})
Copy the code

5.3. Compilation of module components.

The device module is used as an example:

import Vue from 'vue';

const device = {
  namespaced:true.// Here I use the namespace

state: {
// This state structure corresponds to the structure of the VUE component in your device
  For example, leftTree corresponds to the leftTree. Vue component in the device file
  leftTree:{
	  // I usually divide the data required by the component into three categories. Of course, you can configure or not according to your own needs
	  output:{ 
		// Exposed public data
	  },
	  update: {// Whether to update component data
	  },
	  cache: {// The component caches data}}},getters: {
  leftTree_checkedNode: state= >{
    return state.leftTree.output.checkedNode
  },
  leftTree_update_tree:state= >{
    return state.leftTree.update.tree
  }
},

mutations: {
    leftTree_checkedNode(state,val){
      state.leftTree.output.checkedNode=val
  },
    leftTree_update_tree(state,val){
      state.leftTree.update.tree=val
  },
},

actions: {}};export default device;

Copy the code

5.4. Invocation in VUE components.

// Add a namespace to each module
this.$store.state.device.leftTree.output.checkedNode  // This will take a long time
this.$store.getters['device/leftTree_checkedNode']    // If there is no namespace, do not write device/

this.$store.commit('device/leftTree_update_tree'.true) // Execute the mutation method in device. Similarly, device/ is not written without a namespace
Copy the code

The vue. Set method is used when you want to set a complex object for state, but there is no mutation method. Vue. Set can also change the state. However, mutation is generally recommended to facilitate management and maintenance.

6. Usage scenarios

Finally, a brief description of vuex usage scenarios:

  • Data shared by more than two components.
  • Data shared by multiple sibling components.
  • Facilitate the modification of shared data between sibling components.
  • Globally shared data, such as token.
  • Core business data.

Some practical applications

  • Because VUex is saved globally, data is reset only when the page is refreshed. So it can sometimes be used to save part of the information that was saved before the component was destroyed without having to request the data repeatedly.

The last

If you find something wrong or disagreeable, please leave a message. Thank you very much!