[Vuex State Management] Basic use of Vuex; basic use of core concepts State, Getters, Mutations, Actions, and Modules

Directory

  • 1_Application status management
    • 1.1_Status Management
    • 1.2_Complex state management
    • 1.3_Vuex state management
  • 2_Basic use of Vuex
    • 2.1_Installation
    • 2.2_Create Store
    • 2.3_Use store in components
  • 3_Core concept State
    • 3.1_Single state tree
    • 3.2_Component acquisition status
    • 3.3_Use mapState in setup
  • 4_Core Concept Getters
    • 4.1 Basic use of getters
    • 4.2_getters second parameter
    • 4.3_getters return function
    • 4.4_mapGetters’ auxiliary functions
  • 5_Core Concept Mutations
    • 5.1_Use
    • 5.2_Mutation constant type
    • 5.3_Important principles of mutation
  • 6_Core Concept Actions
    • 6.1_Basic use
    • 6.2_Distribution operation
    • 6.3 Asynchronous operations of _actions
  • 7_Core Concept Modules
    • 7.1 Basic use of_module
    • 7.2_Module’s local state
    • 7.3_module namespace
    • 7.4_module modifies or dispatches the root component

1_Application status management

1.1_Status Management

During development, the application needs to process a variety of data, which needs to be stored in a certain location in the application. The management of these data is called state management.

How do you manage your own status in the past?

  • In Vue development, use component development method;
  • When data is defined in a component or used in a setup, this data is called state;
  • This data can be used in the module template, and the module will eventually be rendered into DOM, called View;
  • Some behavioral events will be generated in the module. When processing these behavioral events, the state may be modified. These behavioral events are called actions;

1.2_Complex status management

Applications developed with JavaScript have become increasingly complex:

  • JavaScript needs to manage more and more states and becomes more and more complex;
  • These states include data returned by the server, cached data, data generated by user operations, etc.;
  • It also includes some UI status, such as whether certain elements are selected, whether loading animations are displayed, and the current paging;

The simplicity of one-way data flow can easily be broken when an application encounters multiple components sharing state:

  • Multiple views depend on the same state;
  • Actions from different views require changing the same state;

Can this be accomplished by passing component data?

  • For some simple states, the state can indeed be shared through props passing or Provide;
  • But for complex state management, it is obvious that simply passing and sharing is not enough to solve the problem. For example, how do sibling components share data?

1.3_Vuex state management

Managing changing state is inherently difficult:

  • There will be dependence between states. A change in one state will cause a change in another state. The View page may also cause a change in state;
  • When the application is complex, it becomes very difficult to control and track when, for what reason, and how the state changed;

Therefore, can we consider extracting the internal state of the component and managing it as a global singleton?

  • In this mode, the component tree forms a huge “View”;
  • No matter where it is in the tree, any component can obtain state or trigger behavior;
  • By defining and isolating various concepts in state management, and maintaining the independence between views and states through mandatory rules, your code edges will become more structured and easier to maintain and track;

This is the basic idea behind Vuex, which draws on Flux, Redux, and Elm (pure functional languages, redux has borrowed ideas from it);
Vue officials also recommend using Pinia for state management, follow-up learning

Refer to the pictures on the official website

2_Basic use of Vuex

2.1_Installation

npm install

2.2_Create Store

The core of every Vuex application is the store: The store is essentially a container, which contains most of the state in the application;

What is the difference between Vuex and a pure global object?

  • First: Vuex’s state storage is responsive. When the Vue component reads the state from the store, if the state in the store changes, the corresponding component will also be updated;

  • Second: You cannot directly change the state in the store.

    • The only way to change the state in the store is to explicitly commit a mutation;
    • This makes it easy to track every status change, so that some tools can help better manage the status of the application;

demo:

(1) Create a new folder store under the src folder. In this folder, the index.js file is generally created, or it can be created according to actual development.

src/store/index.js

import {<!-- --> createStore } from 'vuex'

const store = createStore({<!-- -->
  state: () => ({<!-- -->
    counter: 100
  })
})

(2) Register in main.js

import {<!-- --> createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

(3) Called in App.vue

<template>
  <div class="app">
    <!-- counter in store -->
    <h2>App current count: {<!-- -->{<!-- --> $store.state.counter }}</h2>
  </div>
</template>

2.3_Use store in components

Use the store in the component as follows:

  • Used in template template, such as the demo of 2.2
  • Used in options api, such as computed;
  • Used in setup;
<template>
  <div class="app">
      <!-- Use store in template template -->
    <h2>HomeCurrent Count: {<!-- -->{ $store.state.counter }}</h2>
    <h2>Computed current count: {<!-- -->{ storeCounter }}</h2>
    <h2>Setup current count: {<!-- -->{ counter }}</h2>
    <button @click="increment"> + 1</button>
  </div>
</template>

<script>
  export default {<!-- -->
      //Used in options api, such as computed;
    computed: {<!-- -->
      storeCounter() {<!-- -->
        return this.$store.state.counter
      }
    }
  }
</script>

<!-- Use store in setup -->
<script setup>
  import {<!-- --> toRefs } from 'vue'
  import {<!-- --> useStore } from 'vuex'

  const store = useStore()
  const {<!-- --> counter } = toRefs(store.state)
  
  function increment() {<!-- -->
    store.commit("increment")
  }
</script>

3_Core concept State

3.1_Single state tree

Vuex uses a single state tree:

  • One object contains all application-level status;
  • SSOT, Single Source of Truth, is used, which can also be translated into a single data source;

This also means that each application will only contain one store instance. There is no conflict between single-state tree and modularity. The concept of module will be involved later;

Advantages of a single state tree:

  • If status information is saved to multiple Store objects, subsequent management and maintenance will become particularly difficult;
  • Therefore, Vuex also uses a single state tree to manage all states at the application level;
  • A single state tree allows the most direct way to find fragments of a certain state;
  • Moreover, it can also be very convenient to manage and maintain during the subsequent maintenance and debugging process;

3.2_Component acquisition status

In the previous demo, we already know how to get the status in the component.

Of course, if you find that method a bit cumbersome (the expression is too long), you can use computed properties:

 computed: {<!-- -->
      storeCounter() {<!-- -->
        return this.$store.state.counter
      }
    }

However, if there are many states that need to be obtained, you can use the auxiliary function of mapState:

  • MapState method one: object type;
  • MapState method 2: array type;
  • You can also use the spread operator to mix it with the original computed;

3.3_Use mapState in setup

In setup, it is very simple to obtain a single state. Just get the store through useStore and then obtain a certain state.

But how to get the state if using mapState?

(1) By default, Vuex does not provide a very convenient way to use mapState. The following method is not recommended.

<template>
  <div class="app">
    <!-- Use multiple states directly in the template -->
    <h2>name: {<!-- -->{ $store.state.name }}</h2>
    <h2>level: {<!-- -->{ $store.state.level }}</h2>
    <h2>avatar: {<!-- -->{ $store.state.avatarURL }}</h2>
  </div>
</template>

<script setup>
  import {<!-- --> computed } from 'vue'
  import {<!-- --> mapState, useStore } from 'vuex'

  // Complete step by step, the steps are complicated
  const {<!-- --> name, level } = mapState(["name", "level"])
  const store = useStore()
  const cName = computed(name.bind({<!-- --> $store: store }))
  const cLevel = computed(level.bind({<!-- --> $store: store }))
</script>

(2) A function is encapsulated here to simplify the steps

src/hooks/useState.js

import {<!-- --> computed } from 'vue'
import {<!-- --> useStore, mapState } from 'vuex'

export default function useState(mapper) {<!-- -->
  const store = useStore()
  const stateFnsObj = mapState(mapper)
  
  const newState = {<!-- -->}
  Object.keys(stateFnsObj).forEach(key => {<!-- -->
    newState[key] = computed(stateFnsObj[key].bind({<!-- --> $store: store }))
  })

  return newState
}

Use this function

<template>
  <div class="app">
    <!-- Use multiple states directly in the template -->
    <h2>name: {<!-- -->{<!-- --> $store.state.name }}</h2>
    <h2>level: {<!-- -->{<!-- --> $store.state.level }}</h2>
    <h2>avatar: {<!-- -->{<!-- --> $store.state.avatarURL }}</h2>
  </div>
</template>

<script setup>
  import useState from "../hooks/useState"
  //Use useState to encapsulate the function
  const {<!-- --> name, level } = useState(["name", "level"])
</script>

(3) The following usage methods are more recommended

<template>
  <div class="app">
    <!-- Use multiple states directly in the template -->
    <h2>name: {<!-- -->{ $store.state.name }}</h2>
    <h2>level: {<!-- -->{ $store.state.level }}</h2>
    <h2>avatar: {<!-- -->{ $store.state.avatarURL }}</h2>
  </div>
</template>

<script setup>
  import {<!-- --> computed, toRefs } from 'vue'
  import {<!-- --> mapState, useStore } from 'vuex'

  // 3. Deconstruct store.state directly (recommended)
  const store = useStore()
  const {<!-- --> name, level } = toRefs(store.state)
</script>

4_Core concept Getters

Basic use of 4.1_getters

Some attributes may need to be changed before use. In this case, getters can be used.

4.2_getters second parameter

Getters can receive a second parameter

 getters: {<!-- -->
    // 2. In the getters attribute, get other getters
    message(state, getters) {<!-- -->
      return `name:${<!-- -->state.name} level:${<!-- -->state.level} friendTotalAge:${<!-- -->getters.totalAge}`
    }

  }

Return function of 4.3_getters

 getters: {<!-- -->
    // 3. Getters can return a function, and you can pass in parameters when calling this function (understand)
    getFriendById(state) {<!-- -->
      return function(id) {<!-- -->
        const friend = state.friends.find(item => item.id === id)
        return friend
      }
    }
  }

Auxiliary functions of 4.4_mapGetters

You can use mapGetters helper functions

<template>
  <div class="app">
    <h2>doubleCounter: {<!-- -->{ doubleCounter }}</h2>
    <h2>friendsTotalAge: {<!-- -->{ totalAge }}</h2>
    <!-- Get information about a friend based on ID -->
    <h2>Friend information of id-111: {<!-- -->{ getFriendById(111) }}</h2>
    <h2>Friend information of id-112: {<!-- -->{ getFriendById(112) }}</h2>
  </div>
</template>

<script>
  import {<!-- --> mapGetters } from 'vuex'

  export default {<!-- -->
    computed: {<!-- -->
      ...mapGetters(["doubleCounter", "totalAge"]),
      ...mapGetters(["getFriendById"])
    }
  }
</script>

Can also be used in setup

<template>
  <div class="app">
    <h2>message: {<!-- -->{ message }}</h2>
  </div>
</template>

<script setup>
  import {<!-- --> computed, toRefs } from 'vue';
  import {<!-- --> mapGetters, useStore } from 'vuex'

  const store = useStore()

  // 1. Using mapGetters is more troublesome
  // const { message: messageFn } = mapGetters(["message"])
  // const message = computed(messageFn.bind({ $store: store }))

  // 2. Deconstruct directly and wrap it into ref
  // const { message } = toRefs(store.getters)

  // 3. Use computed for a certain getters attribute
  const message = computed(() => store.getters.message)
</script>

5_Core Concept Mutations

The only way to change state in a Vuex store is to submit a mutation

5.1_Use

When submitting a mutation, some data will be carried. At this time, parameters can be used. Note that the payload is an object type.

mutation :{<!-- -->
 add(state,payload){<!-- -->
   statte.counter + = payload
 }
}

submit

$store.commit({<!-- -->
  type: "add",
  count: 100
})

5.2_Mutation constant type

demo:

(1) In mutaition-type.js, define constants

export const CHANGE_INFO = "changeInfo"

(2) Use constants in store

 mutations: {<!-- -->
      [CHANGE_INFO](state, newInfo) {<!-- -->
      state.level = newInfo.level
      state.name = newInfo.name
    }
  }

(3) In use

5.3_Important principles of mutation

An important principle is that mutation must be a synchronous function

  • This is because the devtool tool will record the mutation log;
  • Every mutation is recorded, devtools needs to capture snapshots of the previous state and the next state;
  • However, when performing asynchronous operations in mutations, data changes cannot be tracked;

6_Core Concept Actions

6.1_Basic use

Action is similar to mutation, except that:

  • Action submits mutations rather than directly changing the state;

  • Action can contain any asynchronous operation;

There is a very important parameter context:

  • context is a context object that has the same methods and properties as the store instance;
  • So you can get the commit method from it to submit a mutation, or get state and getters through context.state and context.getters;

For specific cases, please refer to this article: https://blog.csdn.net/qq_21980517/article/details/103398686

6.2_Distribution Operation

Asynchronous operations of 6.3_actions

Action is usually asynchronous. You can know when the action ends by letting the action return a Promise, and then handle the completed operation in the Promise’s then.

demo:

In index.js, take sending a network request as an example

action{<!-- -->
fetchHomeMultidataAction(context) {<!-- -->
      // 1. Return Promise, set then to Promise
      // fetch("http://123.207.32.32:8000/home/multidata").then(res => {<!-- -->
      // res.json().then(data => {<!-- -->
      // console.log(data)
      // })
      // })
      
      // 2.Promise chain call
      // fetch("http://123.207.32.32:8000/home/multidata").then(res => {<!-- -->
      // return res.json()
      // }).then(data => {<!-- -->
      // console.log(data)
      // })
      return new Promise(async (resolve, reject) => {<!-- -->
        // 3.await/async
        const res = await fetch("http://123.207.32.32:8000/home/multidata")
        const data = await res.json()
        
        //Modify state data
        context.commit("changeBanners", data.data.banner.list)
        context.commit("changeRecommends", data.data.recommend.list)

        resolve("aaaaa")
      })
    }
 }

test3.vue in

<script setup>
  import {<!-- --> useStore } from 'vuex'
  // Tell Vuex to initiate a network request
  const store = useStore()
  store.dispatch("fetchHomeMultidataAction").then(res => {<!-- -->
    console.log("then in home is called back:", res)
  })

</script>

7_Core Concept Modules

Basic use of 7.1_module

What is the understanding of Module?

  • Due to the use of a single state tree, all the states of the application will be concentrated into a relatively large object. When the application becomes very complex, the store object may become quite bloated;
  • In order to solve the above problems, Vuex allows the store to be divided into modules;
  • Each module has its own state, mutation, action, getter, and even nested submodules

Partial status of 7.2_module

For mutations and getters inside the module, the first parameter received is the module’s local state object.

7.3_module namespace

By default, actions and mutations inside the module are still registered in the global namespace:

  • This allows multiple modules to respond to the same action or mutation;
  • Getters are also registered in the global namespace by default;

If you want the module to have a higher degree of encapsulation and reusability, you can add namespaced: true to make it a module with a namespace. After the module is registered, all its getters and actions and mutation will automatically adjust the naming according to the path registered by the module.

demo:counter,js

const counter = {<!-- -->
  namespaced: true, //command space
  state: () => ({<!-- -->
    count: 99
  }),
  mutations: {<!-- -->
    incrementCount(state) {<!-- -->
      console.log(state)
      state.count++
    }
  },
  getters: {<!-- -->
    doubleCount(state, getters, rootState) {<!-- -->
      return state.count + rootState.rootCounter
    }
  },
  actions: {<!-- -->
    incrementCountAction(context) {<!-- -->
      context.commit("incrementCount")
    }
  }
}

export default counter

7.4_module modifies or dispatches root components

To modify the state in the root in the action, there are the following methods