超詳細!Vuex手把手教程

鵬多多發表於2021-07-26

1,前言


最近在重溫vue全家桶,再看一遍感覺記憶更深刻,所以專門記錄一下(本文vuex版本為v3.x)。

2,Vuex 是什麼


Vuex是專為Vue.js開發的狀態管理模式。它採用集中式儲存,管理所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化(我的理解就是全域性變數)。

3,5大屬性說明


state

物件型別,類似於例項的 data屬性,存放資料

getters

物件型別,類似於例項的計算屬性 computed

mutations

物件型別,類似於例項的 methods,但是不能處理非同步方法

actions

物件型別,類似於例項的 methods,可以處理非同步方法

modules

物件型別,當state內容比較多時,通過該屬性分割成小模組,每個模組都擁有自己的 state、mutation、action、getter

4,state


儲存在state中的資料和Vue例項中的data遵循相同的規則,必須是純粹的物件。

4.1 直接訪問

this.$store.state.xxx

4.1 使用mapState對映

<template>
	<div id="communication">
		<p>計數:{{ getCount }}</p>
		<p>學校:{{ getSchool('我是引數') }}</p>
	</div>
</template>

<script>
import { mapState } from 'vuex'

export default {
	name: 'Vuex',
	data() {
		return {
			date: 1998
		}
	},
	computed: {
		...mapState({
			// mapState預設會把state當第一個引數傳進來
			getCount: state => state.count,
			getSchool(state) {
				return (val) => {
					return state.school + val + this.date
				}
			}
		})
	},
	mounted() {
		// 直接取值
		console.log(this.$store.state.count)
	}
}
</script>

5,getters


getter的返回值會根據它的依賴被快取起來,且只有當它的依賴值發生了改變才會被重新計算,並且預設接受state作為其第一個引數,也可以接受其他getter作為第二個引數(如下例)

5.1 先在vuex中定義getters

export default new Vuex.Store({
	state: {
		count: 0,
		school: '清華大學'
	},
	getters: {
		// 返回處理後的state值
		getValue(state) {
			return state.count + '!'
		},
		// 返回撥用自身getters處理後的state值
		getGetters(state, getters) {
			return state.school + getters.getValue
		},
		// 接受外部傳參後處理的值(在通過方法訪問時,每次都會去進行呼叫,而不會快取結果)
		getParam(state) {
			return (param) => {
				return state.school + param
			}
		}
	},
	mutations: {},
	actions: {},
	modules: {}
})

5.2 直接獲取值

// 取值
console.log(this.$store.getters.getGetters)
// 傳參取值
console.log(this.$store.getters.getParam('param'))

5.3 使用mapGetters對映

<template>
	<div id="communication">
		<p>計數:{{ getGetters }}</p>
		<p>學校:{{ getParam(date) }}</p>
	</div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
	name: 'Vuex',
	data() {
		return {
			date: 1998
		}
	},
	computed: {
		...mapGetters([
			'getGetters',
			'getParam'
		])
	},
	mounted() {
		// 直接取值
		console.log(this.$store.getters.getGetters)
		console.log(this.getParam(this.date))
	}
}
</script>

6,Mutation


通過呼叫this.$store.commit('xxx'),呼叫mutation中的方法,更改store中的值

6.1,先在mutations中註冊事件

export default new Vuex.Store({
	state: {
		count: 0,
		school: '清華大學'
	},
	getters: {},
	mutations: {
		// 預設state作為第一個引數
		handleAdd(state) {
			state.count++
		},
		// 接受傳參
		handleChange(state, value) {
			state.school = value
		}
	},
	actions: {},
	modules: {}
})

6.2,在元件中呼叫方法commit修改值

<template>
	<div id="communication">
		<p>計數:{{ count }}</p>
		<el-button @click="handleStoreAdd">增加</el-button>
		<el-button @click="handleStoreChange">傳參</el-button>
	</div>
</template>

<script>
import { mapState } from 'vuex'

export default {
	name: 'Vuex',
	data() {
		return {
			school: '武漢大學'
		}
	},
	computed: {
		...mapState([
			'count'
		])
	},
	methods: {
		// 呼叫修改
		handleStoreAdd() {
			this.$store.commit('handleAdd')
		},
		// 傳遞引數修改
		handleStoreChange() {
			this.$store.commit('handleChange', this.school)
		}
	}
}
</script>

6.3,使用常量定義方法名

新建檔案mutation-types.js,定義方法名的常量,並匯出

export const ADD_COUNT = 'ADD_COUNT'
export const CHANGE = 'CHANGE'

在store中

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		count: 0,
		school: '清華大學'
	},
	getters: {},
	mutations: {
		// 預設state作為第一個引數
		[MT.ADD_COUNT](state) {
			state.count++
		},
		// 接受傳參
		[MT.CHANGE](state, value) {
			state.school = value
		}
	},
	actions: {},
	modules: {}
})

在元件中

<template>
	<div id="communication">
		<p>計數:{{ count }}</p>
		<el-button @click="handleStoreAdd">增加</el-button>
		<el-button @click="handleStoreChange">傳參</el-button>
	</div>
</template>

<script>
import { mapState } from 'vuex'
import * as MT from '../../store/mutation-types'
export default {
	name: 'Vuex',
	data() {
		return {
			school: '武漢大學'
		}
	},
	computed: {
		...mapState([
			'count'
		])
	},
	methods: {
		// 呼叫修改
		handleStoreAdd() {
			this.$store.commit(MT.ADD_COUNT)
		},
		// 傳遞引數修改
		handleStoreChange() {
			this.$store.commit(MT.CHANGE, this.school)
		}
	}
}
</script>

6.4,使用mapMutations對映

<template>
	<div id="communication">
		<p>計數:{{ count }}</p>
		<p>計數:{{ school }}</p>
		<el-button @click="handleStoreAdd">增加</el-button>
		<el-button @click="handleStoreChange(schools)">傳參</el-button>
	</div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
	name: 'Vuex',
	data() {
		return {
			schools: '武漢大學'
		}
	},
	computed: {
		...mapState([
			'count',
			'school'
		])
	},
	methods: {
		...mapMutations({
			handleStoreAdd: MT.ADD_COUNT,
			handleStoreChange: MT.CHANGE
		})
	}
}
</script>

7,Action


注意,Action提交的是mutation,而不是直接變更狀態,並且可以包含任意非同步操作

7.1,在store中定義

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		count: 0,
		school: '清華大學'
	},
	getters: {},
	mutations: {
		// 預設state作為第一個引數
		[MT.ADD_COUNT](state) {
			state.count++
		},
		// 接受傳參
		[MT.CHANGE](state, value) {
			state.school = value
		}
	},
	actions: {
		add(context) {
			context.commit(MT.ADD_COUNT)
		}
	},
	modules: {}
})

7.2,在元件中使用

<template>
	<div id="communication">
		<p>計數:{{ count }}</p>
		<el-button @click="actionAdd">增加</el-button>
	</div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
	name: 'Vuex',
	data() {
		return {
			schools: '武漢大學'
		}
	},
	computed: {
		...mapState([
			'count',
			'school'
		])
	},
	methods: {
		...mapMutations({
			handleStoreAdd: MT.ADD_COUNT,
			handleStoreChange: MT.CHANGE
		}),
		// 呼叫action的方法,需要使用$store.dispatch
		actionAdd() {
			this.$store.dispatch('add')
		}
	}
}
</script>

7.3,使用mapActions對映

import { mapActions } from 'vuex'

methods: {
	...mapActions([
		'moduleFn'
	])
}

或者

import { mapActions } from 'vuex'

methods: {
	...mapActions([
		fn: 'moduleFn'
	])
}

7.4,簡化寫法

Action接受一個與store例項具有相同方法和屬性的context引數物件,因此你可以呼叫context.commit提交一個mutation,或者通過context.statecontext.getters來獲取stategetters,利用ES6的解構,可以簡化寫法。

actions: {
  add({ commit, state }) {
    commit(MT.CHANGE, state.school)
  }
}

7.5,執行非同步操作

在vuex中

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		count: 0
	},
	getters: {},
	mutations: {
		// 預設state作為第一個引數
		[MT.ADD_COUNT](state) {
			state.count++
		}
	},
	actions: {
		add({ commit }) {
			return new Promise((resolve, reject) => {
				setTimeout(() => {
					commit(MT.ADD_COUNT)
					resolve()
				}, 1000)
			})
		}
	},
	modules: {}
})

在元件中使用async / await或者then / catch處理非同步

<template>
	<div id="communication">
		<p>計數:{{ count }}</p>
		<el-button @click="actionAdd">增加</el-button>
	</div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
	name: 'Vuex',
	data() {
		return {
			schools: '武漢大學'
		}
	},
	computed: {
		...mapState([
			'count',
			'school'
		])
	},
	methods: {
		...mapMutations({
			handleStoreAdd: MT.ADD_COUNT,
			handleStoreChange: MT.CHANGE
		}),
		// 呼叫action的方法,需要使用$store.dispatch
		async actionAdd() {
			await this.$store.dispatch('add')
			console.log(1998)
		}
	}
}
</script>

8,Modules


當應用變得非常複雜時,store物件就可能變得相當臃腫。這時候可以將store分割成模組,每個模組擁有自己的statemutationactiongetter、甚至是巢狀子模組,從上至下進行同樣方式的分割。

8.1,準備工作

在store目錄下新建Modules資料夾,在Modules資料夾中新建modulesA.jsmodulesB.js,如下圖

目錄

在modulesA.js中寫上區域性模組的statemutationactiongetter,並匯出

const moduleA = {
	state: () => ({
		a: '我是moduleA'
	}),
	getters: {},
	mutations: {},
	actions: {}
}

export default moduleA

然後在storeindex.js中引入,並丟進modules物件裡

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
		count: 0
	},
	getters: {},
	mutations: {},
	actions: {},
	modules: {
		moduleA,
		moduleB
	}
})

8.2,使用modules中注入的模組的state

在元件中直接使用

this.$store.state.moduleA.xxx

在元件中使用mapState對映

<span>{{ moduleA.xxx }}</span>

import { mapState } from 'vuex'

computed: {
	...mapState([
		'moduleA'
	])
}

8.3,使用modules中注入模組的getters

在元件中直接使用

this.$store.getters.getModuleA

在元件中使用mapState對映

<p>{{ getModuleA }}</p>

import { mapGetters } from 'vuex'

computed: {
	...mapGetters([
		'getModuleA'
	])
}

模組內部的getter,接受的引數stategetters是模組的區域性狀態物件,而根節點的狀態會作為第三個引數rootState暴露出來

const moduleA = {
	getters: {
		getModuleA(state, getters, rootState) {
			return state.xxx + '---' + rootState.xxx
		}
	}
}

如果需要帶引數

const moduleA = {
	getters: {
		getModuleA(state, getters, rootState) {
			return (value) => {
				return state.a + '---' + value
			}
		}
	}
}

8.4,使用modules中注入模組的mutations

在元件中直接使用

this.$store.commit('setModuleA') || this.$store.commit('setModuleA', '引數')

在元件中使用mapMutations對映

import { mapMutations } from 'vuex'

methods: {
	...mapMutations([
		openFn: 'setModuleA'
	])
}

模組內部的mutations,預設接受的第一個引數state是模組的區域性狀態物件

const moduleA = {
	mutations: {
		setModuleA(state) {
			state.xxx += 'xxx'
		}
	}
}

如果需要帶引數

const moduleA = {
	mutations: {
		setModuleA(state, value) {
			state.xxx += value
		}
	}
}

8.5,使用modules中注入模組的actions

在元件中直接使用

this.$store.dispatch('xxx')

在元件中使用mapActions對映

import { mapActions } from 'vuex'

methods: {
	...mapActions([
		'moduleA'
	])
}

或者重新命名

import { mapActions } from 'vuex'

methods: {
	...mapActions({
		fn: 'moduleA'
	})
}

對於模組內部的action,區域性狀態通過context.state暴露出來,根節點狀態則為context.rootState

const moduleA = {
  // ...
  actions: {
    fn ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

8.6,名稱空間

預設情況下,模組內部的actionmutationgetter是註冊在全域性名稱空間的,這樣使得多個模組能夠對同一mutationaction作出響應。如果希望模組具有更高的封裝度和複用性,可以通過給模組新增namespaced: true的方式使其成為帶名稱空間的模組。當模組被註冊後,它的所有getteractionmutation都會自動根據模組註冊的路徑調整命名。

8.6.1,使用

先在模組moduleB.js中新增namespaced: true

const moduleB = {
	namespaced: true,
	state: () => ({
		b: '我是moduleB'
	}),
	mutations: {},
	actions: {},
	getters: {}
}

export default moduleB

storeindex.js

import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

export default new Vuex.Store({
	state: {},
	getters: {},
	mutations: {},
	actions: {},
	modules: {
		moduleA,
		moduleB
	}
})

如果在元件中使用名稱空間,需要帶上空間名稱,mapState, mapGetters, mapMutationsmapActions用法一樣。

<script>
import { mapState, mapGetters, mapMutations } from 'vuex'

export default {
	name: 'Vuex',
	data() {
		return {}
	},
	computed: {
		// 此處注入的是moduleA模組的資料
		...mapState('moduleA', [
			'a'
		]),
		// 需要注入moduleB模組,就再寫一個
		...mapState('moduleB', [
			'b'
		])
	},
	mounted() {
		// 直接使用
		console.log(this.$store.state.moduleA.a)
		console.log(this.$store.state.moduleB.b)
	},
	methods: {}
}
</script>

8.6.2 ,在帶名稱空間的模組中內訪問全域性內容

如果你希望使用全域性的stategetterrootStaterootGetters會作為第三和第四引數傳入getter,也會通過context物件的屬性傳入action。若需要在全域性名稱空間內分發action或提交mutation,將{ root: true }作為第三引數傳給dispatchcommit即可

const moduleA = {
	namespaced: true,
	state: () => ({
		a: '我是moduleA'
	}),
	getters: {
		getModuleA(state, getters, rootState, rootGetters) {
			// 使用全域性名稱空間的state或getters
			return state.a + rootState.count
		}
	},
	mutations: {
		setModuleA(state) {
			console.log(state.a)
		}
	},
	actions: {
		addM({ state, commit, dispatch, rootState, rootGetters }) {
			console.log(rootState)
			console.log(rootGetters)
			// 呼叫全域性名稱空間的方法
			dispatch('rootFunction', null, { root: true })
		}
	}
}

export default moduleA

8.6.3,在帶名稱空間的模組註冊全域性action

在帶名稱空間的模組註冊全域性action,需要新增root: true,並將這個action的定義放在函式handler中,其中,handler的第一個引數namespacedContext就是action中的Context引數

const moduleA = {
	namespaced: true,
	state: () => ({
		a: '我是moduleA'
	}),
	getters: {},
	mutations: {},
	actions: {
		rootFn: {
			root: true,
			handler(namespacedContext, param) {
				console.log(namespacedContext.state)
			}
		}
	}
}

export default moduleA

如果看了覺得有幫助的,我是@鵬多多,歡迎 點贊 關注 評論;END

面向百度程式設計

公眾號

weixinQRcode.png

往期文章

個人主頁

相關文章