This is the 28th day of my participation in the August Text Challenge.More challenges in August

In development, our application needs to deal with all kinds of data, which needs to be stored in a certain location in our application. The management of this data is called state management

In Vue development, we use a componentized development approach

In the component we define data or in the setup we return data for use, which we call state

We can use this data in the template module, which will eventually be rendered into a DOM called a View

In the module we will generate some action events, and when we handle these action events, it is possible to change the state, which we call actions

Applications developed in JavaScript have become increasingly complex, and the simplicity of one-way data flow can easily be compromised when our application encounters state shared by multiple components

  • Multiple views depend on the same state
  • Actions from different views need to change the same state

Managing the changing state itself at this point is very difficult:

  • States will depend on each other, the change of one state will cause the change of another state, the View page may also cause the change of state
  • When an application is complex, when and for what reason does state change, and how does it change, and become extremely difficult to control and track

At this point, can we consider pulling out the internal state of the component and managing it as a global singleton, that is, turning the data into a global object for use

  • In this mode, our component tree constitutes a giant “try View”
  • No matter where in the tree, any component can get state or trigger behavior
  • By defining and isolating concepts in state management and enforcing rules to maintain independence between views and states, our code edges become more structured and easier to maintain and track

use

#Install -- If you want to use vex4.x, you need to add the next version when installing
npm i vuex@next
Copy the code

We usually store the code we write to manipulate vuex in the Store folder

store

Create the Store

At the heart of every Vuex app is the Store:

  • A Store is essentially a container that contains most of the states in your app.

Differences between Vuex and pure global objects:

  • Vuex's state storage is reactive
    • When a Vue component reads state from a store, if the state in the store changes, the corresponding component is updated
  • Changing state directly in the Store is not recommended
    • The only way to change the state in the store is to show commit mutation
    • This allows us to easily track each state change, which allows us to use tools such as devTool to better manage the application state

main.js

import { createApp } from 'vue'
import App from './App.vue'

import router from './routes'
import store from './store'

// Vuex is essentially a plug-in to Vue -- mounting it to Vue produces a $store object on all instances to help us access Vuex
createApp(App).use(router).use(store).mount('#app')
Copy the code

v1

store.js

import { createStore } from 'vuex'

const store = createStore({
 // state is a function that returns an object
 // All vuex data is stored in the object returned by state
 state() {
   return {
     counter: 0}}})export default store
Copy the code

App.vue

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="increment">+ 1</button>
    <button @click="decrement">- 1</button>
  </div>
</template>

<script>
export default {
  name: 'App'.methods: {
    increment() {
      Vuex does not recommend doing this, although this is not an error
      this.$store.state.counter++
    },

    decrement() {
      this.$store.state.counter--
    },
  }
}
</script>
Copy the code

v2

store.js

import { createStore } from 'vuex'

const store = createStore({
 state() {
   return {
     counter: 0}},// Modify state via the mutations function
 mutations: {
   // The function in mutations will be called back by VUex at the appropriate time
   // The state object of the current vuEX instance is passed in as an argument
   increment(state) {
     state.counter++
   },
   decrement(state) {
    state.counter--
  }
 }
})

export default store
Copy the code

App.vue

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="increment">+ 1</button>
    <button @click="decrement">- 1</button>
  </div>
</template>

<script>
export default {
  name: 'App'.methods: {
    increment() {
      // Use the commit function to trigger the corresponding function in mutations
      this.$store.commit('increment')},decrement() {
      this.$store.commit('decrement')}}}</script>
Copy the code

Single state tree

Vuex uses a single state tree:

  • A single object contains all the state at the application level
  • SSOT, Single Source of Truth, can also be translated into a Single data Source
  • This also means that each app will contain only one Store instance
  • We can use the Module to further split the Store

Advantages of a single state tree:

  • If your state information is stored in multiple Store objects, it becomes extremely difficult to manage, maintain, and so on
  • A single state tree allows us to find a fragment of a state in the most direct way, and it is also very convenient to manage and maintain in the later maintenance and debugging process

mapState

It would be tedious to access the store property each time in the template using $store.state. XXX, so vuex provides a helper function for mapState

<template>
  <div>
    <h2>{{ name }}</h2>
    <h2>{{ age }}</h2>
    <h2>{{ counter }}</h2>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'App'.computed: {
    // 1. Use method 1, pass an array as an argument. mapState(['name'.'age'.'counter'])}}</script>
Copy the code
<template>
  <div>
    <h2>{{ sName }}</h2>
    <h2>{{ sAge }}</h2>
    <h2>{{ sCounter }}</h2>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'App'.computed: {
    // Use method 2: pass objects
    // 1. You can customize the name of the store member to be used
    // 2. The obtained data can be processed twice. mapState({sName: state= > state.name,
      sAge: state= > state.age,
      sCounter: state= > state.counter * 2,}}}</script>
Copy the code

MapState is generally used in conjunction with computed

// mapState returns the object, key is the attribute name, and value is a get function. The get function will fetch the corresponding attribute value through this.$store. mapState(['name'.'age'.'counter'])

// The actual compiled result is an object similar to the following (pseudocode)
{
  name() {
    return this,$store.name
  },
    
  age() {
    return this,$store.age
  },
    
  counter() {
    return this,$store.counter
  }
}
Copy the code

Use mapState in setup

hooks/useMapState.js

import { mapState, useStore } from 'vuex'
import { computed } from 'vue'

export default function(mapper) {
    The store object can be retrieved from the setup function using the useStore hook function
    const store = useStore()

    // The return structure of mapState parameters is the same whether they are objects or arrays
    // {key: get, key: get}
    // So we export the method parameters to support both objects and functions
    const mapStateFns = mapState(mapper)
    const storeState = {}


    Object.keys(mapStateFns).forEach(key= > {
      // The reason for using computed wrapping is to export the value of an object
      // Use a ref object exported through a computed function, so that when data changes in the store
      // Automatically listen and automatically update all dependencies
      storeState[key] = computed(mapStateFns[key].bind({ $store: store }))
    })

    return storeState
}
Copy the code

Users of the hook

<template>
  <div>
    <h2>{{ name }}</h2>
    <h2>{{ age }}</h2>
  </div>
</template>

<script>
import useMapState from './hooks/useMapState'

export default {
  name: 'App'.setup() {


    return {
      // Custom hook functions support both arrays and objects. useMapState(['name']),
      ...useMapState({
        age: store= > store.age
      })
    }
  }
}
</script>
Copy the code

getters

Some attributes may need to be changed to be used later, so getters can be used instead

Getters is similar to computed in store

The basic use

store.js

import { createStore } from 'vuex'

const store = createStore({
 state() {
   return {
     books: [{name: 'book1'.price: 32.count: 3
       },

       {
        name: 'book2'.price: 45.count: 5
      },

      {
        name: 'book3'.price: 54.count: 2}}},getters: {
   // Getters defines functions
   /* 1. Parameter 1: state object 2. Parameter 2: getters object, which can use other 'calculated values' */ when performing calculations in getters
   totalPrice(state, getters) {
    return (state.books.reduce((total, book) = > total + book.price * book.count, 0) * getters.discount).toFixed(2)},discount() {
     return 0.95}}})export default store
Copy the code

The user

<template>
  <div>
    <! TotalPrice is a function, just like calculating attributes, but it can be used as an attribute.
    <h2>{{ $store.getters.totalPrice }}</h2>
  </div>
</template>
Copy the code

A lot of times we might want to add constraints when we’re doing calculations,

At this point we can ask getters to return a function that will receive the parameters we need

store.js

import { createStore } from 'vuex'

const store = createStore({
 state() {
   return {
     books: [{name: 'book1'.price: 32.count: 3
       },

       {
        name: 'book2'.price: 45.count: 5
      },

      {
        name: 'book3'.price: 54.count: 2}}},getters: {
   totalPrice(state) {
    // Returns a function that allows the getter function to accept arguments passed in
    return v= > {
      return (state.books.reduce((total, book) = > book.count < v ? total + book.price * book.count : 0.0)).toFixed(2)}}}})export default store
Copy the code

The user

<template>
  <div>
    <! -- Pass parameters -->
    <h2>{{ $store.getters.totalPrice(5) }}</h2>
  </div>
</template>
Copy the code

mapGetters

Like mapStore, Vuex provides the mapGetters function for easy use

An array of writing

import { mapGetters } from 'vuex'

export default {
  name: 'App'.computed: {
    ...mapGetters(['totalPrice'])}}Copy the code

Object syntax

import { mapGetters } from 'vuex'

export default {
  name: 'App'.computed: {
    ...mapGetters({
      // The name of the key is vlaue
      // There is no need to pass a function, this is not consistent with the mapStore function object writing
      totalPrice: 'totalPrice'}}})Copy the code

It’s the same idea as the hook functions encapsulated in mapStore

import { mapGetters, useStore } from 'vuex'
import { computed } from 'vue'

export default function(mapper) {
    const store = useStore()

    const mapStateFns = mapGetters(mapper)
    const storeState = {}


    Object.keys(mapStateFns).forEach(key= > {
      storeState[key] = computed(mapStateFns[key].bind({ $store: store }))
    })

    return storeState
}
Copy the code

Integrate useGetters and useStore

useMapper.js

import { mapGetters, mapState, useStore } from 'vuex'
import { computed } from 'vue'

export default function(mapper, mapFn) {
    const store = useStore()

    const mapStateFns = mapFn === 'state' ? mapState(mapper) : mapGetters(mapper)
    const storeState = {}


    Object.keys(mapStateFns).forEach(key= > {
      storeState[key] = computed(mapStateFns[key].bind({ $store: store }))
    })

    return storeState
}
Copy the code

useState.js

import useMapper from './useMapper'

export default function(mapper) {
   return useMapper(mapper, 'state')}Copy the code

useGetters.js

import useMapper from './useMapper'

export default function(mapper) {
   return useMapper(mapper, 'getter')}Copy the code

index.js

import useGetters from './useMapper'
import useState from './useState'

export {
  useGetters,
  useState
}
Copy the code

The user

<template>
  <div>
    <h2>{{ totalPrice(5) }}</h2>
  </div>
</template>

<script>
import { useGetters } from './hooks'

export default {
  name: 'App'.setup() {
    return {
      ...useGetters(['totalPrice'])}}}</script>
Copy the code

mutations

Parameter passing

store.js

import { createStore } from 'vuex'

const store = createStore({
 state() {
   return {
     counter: 0}},mutations: {
   incrementN(store, payload) {
     store.counter += payload.step
   },
   decrementN(store, payload) {
    store.counter -= payload.step
   }
 }
})

export default store
Copy the code

The user

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="increment">+ 10</button>
    <button @click="decrement">- 10</button>
  </div>
</template>

<script>
import { useStore } from 'vuex'

export default {
  name: 'App'.setup() {
    const store = useStore()

    const increment = () = > store.commit('incrementN', { step: 10 })

    // This is another way to commit
    const decrement = () = > store.commit({
      type: 'decrementN'.step: 10
    })

    return {
      increment,
      decrement
    }
  }
}
</script>
Copy the code

mapMutations

Like mapGetters and mapStore, VUex provides an auxiliary function, mapMutations

Options API

An array of writing

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="incrementN({step: 10})">+ 10</button>
    <button @click="decrementN({step: 10})">- 10</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  name: 'App'.methods: {
    // Note: the function deconstructed by mapMutations does not need to be given to computed
    MapMutations returns an object similar to {key: event handler, key: event handler}
    // Therefore, the function returned by mapMutations can be directly combined into methods for direct use. mapMutations(['incrementN'.'decrementN'])}}</script>
Copy the code

Object to write

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="increment({step: 10})">+ 10</button>
    <button @click="decrement({step: 10})">- 10</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  name: 'App'.methods: {
    ...mapMutations({
      increment: 'incrementN'.decrement: 'decrementN'}}})</script>
Copy the code

Use in composition API

Array syntax

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="incrementN({step: 10})">+ 10</button>
    <button @click="decrementN({step: 10})">- 10</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  name: 'App'.setup() {
    const mutations = mapMutations(['incrementN'.'decrementN'])

    return {
      ...mutations
    }
  }
}
</script>
Copy the code

Object syntax

<template>
  <div>
    <h2>{{ $store.state.counter }}</h2>
    <button @click="increment({step: 10})">+ 10</button>
    <button @click="decrement({step: 10})">- 10</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  name: 'App'.setup() {
    const mutations = mapMutations({
      increment: 'incrementN'.decrement: 'decrementN'
    })

    return {
      ...mutations
    }
  }
}
</script>
Copy the code