Large front end - vue - vuex state management

Posted by kilby on Sun, 06 Feb 2022 20:38:07 +0100

1: Communication mode between components

state: the data source that drives the application.

View: map state to view declaratively.

actions: respond to state changes caused by user input on the view.

Status management:

Communication mode between components:
  1. Value transfer from parent component to child (parent-child component)
    1. Receive data through props in sub components. 2. Transfer values to child components through corresponding attributes in the parent component

Subcomponents

<template>
  <div>
    <h1>Props Down Child</h1>
    <h2>{{ title }}</h2>
  </div>
</template>

<script>
export default {
 // props receives the parameters passed by the parent component
  // props: ['title'],
  props: {
    title: String
  }
}
</script>

Parent component

<template>
  <div>
    <h1>Props Down Parent</h1>
    // Pass title to child component
    <child title="My journey with Vue"></child>
  </div>
</template>

<script>
import child from './01-Child'
export default {
  components: {
    child
  }
}
</script>

  1. The child component passes values to the parent component
    Sub components:
    this.$emit('enlargeText', 0.1)
<template>
  <div>
    <h1 :style="{ fontSize: fontSize + 'em' }">Props Down Child</h1>
    <button @click="handler">Text increase</button>
  </div>
</template>

<script>
export default {
  props: {
    fontSize: Number
  },
  methods: {
    handler () {
     // Use the event passed by the parent component in the child component and pass the value
      this.$emit('enlargeText', 0.1)
    }
  }
}
</script>

Parent component:

<template>
  <div>
    <h1 :style="{ fontSize: hFontSize + 'em'}">Event Up Parent</h1>

    There is no need to change the text here
	// @enlargeText="enlargeText" pass events to child components
    <child :fontSize="hFontSize" @enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" @enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" @enlargeText="hFontSize += $event"></child>
  </div>
</template>

<script>
import child from './02-Child'
export default {
  components: {
    child
  },
  data () {
    return {
      hFontSize: 1
    }
  },
  methods: {
    enlargeText (size) {
      this.hFontSize += size
    }
  }
}
</script>

  1. Value transfer between unrelated components
    It is also the way to use events: bus: event center

eventBus.js

import Vue from 'vue'
export default new Vue()

Sub assembly 1:

bus.$emit('numchange', this.value) bus.$emit trigger event, numchange: custom event name, this Parameter passed by value

<template>
  <div>
    <h1>Event Bus Sibling01</h1>
    <div class="number" @click="sub">-</div>
    <input type="text" style="width: 30px; text-align: center" :value="value">
    <div class="number" @click="add">+</div>
  </div>
</template>

<script>
import bus from './eventbus'

export default {
  props: {
    num: Number
  },
  created () {
    this.value = this.num
  },
  data () {
    return {
      value: -1
    }
  },
  methods: {
    sub () {
      if (this.value > 1) {
        this.value--
        //  bus.$emit('numchange', this.value) triggers an event
        // numchange: custom event name, this Parameter passed by value
        bus.$emit('numchange', this.value)
      }
    },
    add () {
      this.value++
      bus.$emit('numchange', this.value)
    }
  }
}
</script>

<style>
.number {
  display: inline-block;
  cursor: pointer;
  width: 20px;
  text-align: center;
}
</style>

Sub assembly 2
bus.$on: Register: custom event numchange, () = > {}: callback function

<template>
  <div>
    <h1>Event Bus Sibling02</h1>

    <div>{{ msg }}</div>
  </div>
</template>

<script>
import bus from './eventbus'
export default {
  data () {
    return {
      msg: ''
    }
  },
  created () {
  
    bus.$on('numchange', (value) => {
      this.msg = `You have chosen ${value}Pieces of goods`
    })
  }
}
</script>
  1. Review of communication methods between components - obtain sub components through ref
    Two functions of ref:
    1. Use ref on ordinary html tags to get DOM
    2. Use ref on the component tag to get the component instance
    child.vue
<template>
  <div>
    <h1>ref Child</h1>
    <input ref="input" type="text" v-model="value">
  </div>
</template>

<script>
export default {
  data () {
    return {
      value: ''
    }
  },
  methods: {
    focus () {
     // Using ref on plain html
      this.$refs.input.focus()
    }
  }
}
</script>

parent.vue

<template>
  <div>
    <h1>ref Parent</h1>

    <child ref="c"></child>
  </div>
</template>

<script>
import child from './04-Child'
export default {
  components: {
    child
  },
  mounted () {
  	// Use ref on the component to obtain the instance of the component
    this.$refs.c.focus()
    this.$refs.c.value = 'hello input'
  }
}
</script>
  1. State management scheme - Vuex
    Vuex: centralized state management
    The attribute of the state cannot be changed directly in the component. If you want to change it, you need to modify the definition function in the actions in the store. The advantage of this is that it can record all changes of the data in the store.

Problem solved: multiple views depend on the same state
Behaviors from different views need to change the same state

store.js

// State: stored state
// setUserNameAction: used to change the state when interacting with the user through the view.
// debug: convenient for debugging. If it is true, the log will be printed when modifying data through action.
export default {
  debug: true,
  state: {
    user: {
      name: 'xiaomao',
      age: 18,
      sex: 'male'
    }
  },
  setUserNameAction (name) {
    if (this.debug) {
      console.log('setUserNameAction triggered: ', name)
    }
    this.state.user.name = name
  }
}

Use in components:

  1. Introducing store
  2. Use store state. user
<template>
  <div>
    <h1>componentA</h1>
    user name: {{ sharedState.user.name }}
    <button @click="change">Change Info</button>
  </div>
</template>

<script>
// 1. Introduce store
import store from './store'
export default {
  methods: {
    change () {
    // Use the action in the store to change the data
      store.setUserNameAction('componentA')
    }
  },
  data () {
    return {
      privateState: {},
      // Use: store state
      sharedState: store.state
    }
  }
}
</script>

vuex core concepts and basic usage

What is vuex?
vuex is a state management library specially designed for vuejs.
vuex uses a centralized way to store the states that need to be shared.
The role of vuex is to manage state, solve complex component communication and data sharing.
vuex is integrated into devtools and provides time travel history rollback function.

When to use vuex?
vuex is not necessary.
Large single page applications: 1 Multiple views depend on the same state. 2. Behaviors from different views need to change the same state.

The core concept of vuex:


Description of the above figure:
State: store data in state
vue components: bind the state to the component, render it to the user interface and show it to the user. The user can interact with the view, for example, click the button (add to the shopping cart): distribute actions through the dispatch, and do not submit changes directly here, because asynchronous operations can be done in the actions (asynchronous requests need to be sent when purchasing, and after the asynchronous requests are completed, record the change of status by submitting changes to the changes)
Changes: changes must be synchronized. All state changes must be through changes. The purpose of this is to track all state changes through changes. It is easier to analyze the internal state changes of the application when reading the code. It can also record each state change to realize advanced debugging functions.

Core concepts:
Store:
State: the state of the response
Getter: it is a bit similar to the calculation property in vue, which is convenient to derive other values from one property. Internally, the calculation results can be cached and recalculated only when the dependent state changes.
Mutation: the change of status must be completed by submitting mutation.
Actions: action is similar to mutation, except that action can perform asynchronous operations. When changing the state internally, you need to submit the mutation
Module: module. When the data becomes very large, the module mode can be adopted.

If used? Overall code structure
store.js

import Vue from 'vue'
import Vuex from 'vuex'


Vue.use(Vuex)

export default new Vuex.Store({

  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
})

main.js

import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

console.log(store)

Use state:

import Vue from 'vue'
import Vuex from 'vuex'


Vue.use(Vuex)

export default new Vuex.Store({

  state: {
  	count:0,
  	msg: 'hello vuex'
  },
  getters: {
  	// Output msg in reverse order
  	reverseMsg(state) {
		return state.msg.split('').reverse().join()
	}
  },
  mutations: {
  },
  actions: {
  },
})

Use in components:
Method 1: $store state. count

<template>
  <div id="app">
     count: {{ $store.state.count }}
    msg: {{ $store.state.msg }}
{{count}}
{{msg}}
   </div>
<template>

Method 2:mapState

import { mapState } from 'vuex'
computed: {
	// Method 1: write as an array
	//...mapState(['count', 'msg'])
	// Method 2: Writing in function mode
	count: state => state.count,
	msg: state => state.msg,
	// Method 3: write in the form of object, which can solve the problem of naming conflict
	...mapState({num: 'count', message: 'msg'}),
}

Gutter (display data in view): similar to calculating attributes, you want to process the data in the store and then display it on the page.

 getters: {
  	// Output msg in reverse order
  	reverseMsg(state) {
		return state.msg.split('').reverse().join()
	}
  },

Use in components:
Method 1: directly use in the template: $store getters. Reversemsg mode 2: use mapGetters`

<div>
	{{$store.getters.reverseMsg}}
	{{reverseMsg}}
</div>
<script>
	import { mapGetters} from 'vuex'
	computed: {
		...mapGetters([reverseMsg])
	}
</script>

Modification status: how to modify the status? The status modification must be submitted to the mutation, and the mutation must be executed synchronously, so as to ensure that all status modifications can be collected in the mutation. Do not use asynchronous mode in mutation. If asynchronous mode is required, use action
Usage:

// Click the [add] button
mutations: {
	increate(state, paylod) {
		state.count + = paylod
	}
}

Use in components:
Method 1: $store commit('increate', 2)

<button @click="$store.commit('increate', 2)">increase</button>

Method 2: mapMutations

<button @click="increate(3)">increase</button>


import {mapMutations} from 'Vuex'
method:{
	...mapMutations(['increate']),
}

Action: if you need to use asynchronous operation, use action. If you need to change the state after asynchrony, you need to raise mutation to modify the state. Because all state changes use mutation. For example, if you need to obtain commodity data, you need to send a request in action. After the asynchronous execution is completed, you need to submit mutation after obtaining commodity data and record the data in state.

Action usage:

actions: {
	increateAsync(context, payload) {
	// setTimeout is an asynchronous task
      setTimeout(() => {
        context.commit('increate', payload)
      }, 2000)		
	}
}

Use in components:
Method 1: $store dispatch('increateAsync', 5)
Method 2: mapActions

<button @click="$store.dispatch('increateAsync', 5)">Action</button> 
<button @click="increateAsync(6)">Action</button>

import { mapActions } from 'vuex'
methods: {
    ...mapActions(['increateAsync']),
  }

Module: modular processing state

Directory structure:

index.js

import Vue from 'vue'
import Vuex from 'vuex'
// Modular processing state
import products from './modules/products'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  getters: {
    reverseMsg (state) {
      return state.msg.split('').reverse().join('')
    }
  },
  mutations: {
    increate (state, payload) {
      state.count += payload
    }
  },
  actions: {
    increateAsync (context, payload) {
      setTimeout(() => {
        context.commit('increate', payload)
      }, 2000)
    }
  },
  modules: {
    products,
    cart
  }
})

modules/cart.js

const state = {}
const getters = {}
const mutations = {}
const actions = {}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

modules/products.js

const state = {
  products: [
    { id: 1, title: 'iPhone 11', price: 8000 },
    { id: 2, title: 'iPhone 12', price: 10000 }
  ]
}
const getters = {}
const mutations = {
  setProducts (state, payload) {
    state.products = payload
  }
}
const actions = {}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

Use the method and state attribute of the module in the component
Method 1: $store state. products. products. The first products refers to the name of the module, and the second products refers to the attributes in state
Method 2:mapMutations

products: {{ $store.state.products.products }} <br>
<button @click="$store.commit('setProducts', [])">Mutation</button>

Method 2:

 products: {{ products }} <br>
<button @click="setProducts([])">Mutation</button>

import {mapMutations} from 'vuex'
computed: {
	// Note: the first parameter of mapState: products refers to the name of the module
	// The second parameter: ['products'] refers to the attribute in state
    ...mapState('products', ['products'])
  },
methods: {
	// Note: the first parameter of mapMutations: products refers to the name of the module
	// The second parameter: ['setProducts'] refers to the method name in mutation
    ...mapMutations('products', ['setProducts'])
  }

vuex strict mode:
All state changes must go through mutation, but this is only a verbal agreement. If you add $store. In the component state. Count = 1 can also be modified without throwing an error, but the modification of state becomes untraceable, and the devtools page cannot be tracked. After the strict mode is turned on, if it is modified in this way, an error will be thrown.

Enable strict mode: strict: process env. NODE_ ENV !== ' production',

import Vue from 'vue'
import Vuex from 'vuex'
import products from './modules/products'
import cart from './modules/cart'

Vue.use(Vuex)

export default new Vuex.Store({
 // Turn on strict mode
  strict: process.env.NODE_ENV !== 'production',
  state: {
    count: 0,
    msg: 'Hello Vuex'
  },
  getters: {
    reverseMsg (state) {
      return state.msg.split('').reverse().join('')
    }
  },
  mutations: {
    increate (state, payload) {
      state.count += payload
    }
  },
  actions: {
    increateAsync (context, payload) {
      setTimeout(() => {
        context.commit('increate', payload)
      }, 2000)
    }
  },
  modules: {
    products,
    cart
  }
})

Error report after opening:

Do not turn it on in production mode, because it will affect the performance of deep monitoring.

Shopping cart case

Shopping cart function:
1. The interface written by node is used: node server js
2.
Page:


After refreshing the shopping cart page, the data in the shopping cart is stored locally.

Project code address:
Three components: product list component (products.vue),
Shopping cart component (cart.vue),
Hover the mouse [my shopping cart] to display the commodity components in the shopping cart (pop-cart.vue)

Item list component:

Simulation implementation vuex

myVuex/index.js

let _Vue = null
class Store {
  constructor (options) {
    const {
      state = {},
      getters = {},
      mutations = {},
      actions = {}
    } = options
    // _ Vue.observable(state): turn data into responsive data
    this.state = _Vue.observable(state)
    this.getters = Object.create(null)
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        get: () => getters[key](state)
      })
    })
    this._mutations = mutations
    this._actions = actions
  }

  commit (type, payload) {
    this._mutations[type](this.state, payload)
  }

  dispatch (type, payload) {
    this._actions[type](this, payload)
  }
}

function install (Vue) {
  _Vue = Vue
  _Vue.mixin({
    beforeCreate () {
      if (this.$options.store) {
        _Vue.prototype.$store = this.$options.store
      }
    }
  })
}

export default {
  Store,
  install
}

Topics: Javascript Front-end Vue.js