Vuex詳細介紹和使用方法
一、什么是Vuex
官網解釋如下:
Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應用的所有組件的狀態(tài),并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。Vuex 也集成到 Vue 的官方調試工具devtools extension,提供了諸如零配置的 time-travel 調試、狀態(tài)快照導入導出等高級調試功能。
那么什么是“狀態(tài)管理模式”呢?
狀態(tài)自管理應用包括以下幾個部分:
- state:驅動應用的數據源;
- view:以聲明方式將state映射到視圖;
- action:響應在view上的用戶輸入導致的狀態(tài)變化;
看下面這張表示“單向數據流”理念的簡單示意圖:

從上面的圖中可以看出:整個系統(tǒng)的數據流是單向的:數據(即state)去驅動視圖(view)的更新,用戶在view上面進行一些操作觸發(fā)action,通過action去更新state,而不是視圖直接更新state。
二、運行機制
下面來看一張官網上面的Vuex運行機制圖:

從圖中可以看出,Vuex不在和組件強相關。運行機制:Vuex提供數據(state),來驅動視圖(這里指的是Vue組件),視圖通過Dispatch派發(fā)Action,在Action中可以進一步做一些異步的操作(例如通過ajax請求后端的接口數據),然后通過Commit提交給Mutations,由Mutations去最終更改state。那么為什么要經過Mutations呢?這是因為我們要在Vue調試工具(Devtools)中記錄數據的變化,這樣可以通過插件去進行進一步的調試。所以說Mutations中只能是純同步的操作,如果是有異步操作,那么就需要在Actions中進行處理。如果說沒有異步操作,那么可以直接由組件進行Commit操作Mutations。如下圖所示:

三、創(chuàng)建項目
1、使用腳手架搭建Vue項目
在命令行里面輸入如下命令即可創(chuàng)建一個Vue項目:
vue init webpack vuex-demo
如下圖所示:

然后回車進行安裝,選擇默認配置即可,出現如下面所示的界面表示創(chuàng)建成功:

2、安裝Vuex
新創(chuàng)建的項目里面默認不會安裝Vuex,如果要使用Vuex,需要進行手動安裝,輸入下面的命令:
npm install vuex --save
如下圖所示:

注:--save表示進行全局安裝
3、啟動項目
在第一步創(chuàng)建成功項目以后,最后會提示如何啟動項目,如下面所示:

這兩行命令表示先進入項目的根目錄,然后輸入npm run dev命令即可啟動項目,如下圖所示:

在瀏覽器窗口里面輸入:http://localhost:8080進行瀏覽:

4、配置使用Vuex
4.1、創(chuàng)建store文件夾
在src目錄下面創(chuàng)建一個store文件夾,該文件夾用來存放所有與狀態(tài)管理有關的文件,然后創(chuàng)建一個index.js文件,Store對象寫在index.js文件里面,代碼如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建store對象
const store=new Vuex.Store({
})
// 導出創(chuàng)建的store對象
export default store;4.2、配置全局使用store對象
在main.js里面配置全局使用store對象:
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
// 引入index.js文件
import store from './store/index'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,// 配置全局使用store對象
components: { App },
template: '<App/>'
})到此為止,一個Vuex項目創(chuàng)建完成,并且安裝了Vuex,下面就可以使用該項目演示如何使用Vuex進行狀態(tài)管理了。
四、Vuex核心概念
1、state
在index.js文件里面添加state,代碼如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state導出
state
})
// 導出創(chuàng)建的store對象
export default store; 在test.vue中使用展示初始值:
<template>
<p>初始值:{{this.$store.state.num}}</p>
</template>
<script>
export default {
name:'test'
}
</script>頁面效果:

注:還可以通過計算屬性獲取num的值:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
</div>
</template>
<script>
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
}
}
}
</script>效果如圖所示:

2、mutations
在上面使用了state可以獲取到屬性的值,那么如何修改state中屬性的值呢?這時候就要用到mutations了。官網解釋如下:
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似于事件:每個 mutation 都有一個字符串的事件類型 (type)和 一個回調函數 (handler)。這個回調函數就是我們實際進行狀態(tài)更改的地方,并且它會接受 state 作為第一個參數。還是以上面的為例子。
修改index.js文件,添加mutations:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 改變狀態(tài),通過commit
var mutations={
// state作為第一個參數
ChangeState(state){
// 自增1
state.num++;
}
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state
state,
// 導出mutations
mutations
})
// 導出創(chuàng)建的store對象
export default store;修改test.vue文件:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
</div>
</template>
<script>
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
}
}
}
</script>效果:

注:改變狀態(tài)還可以傳遞參數進行修改,看下面的例子:
修改index.js文件如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 改變狀態(tài),通過commit
var mutations={
// state作為第一個參數
ChangeState(state){
// 自增1
state.num++;
},
// state作為第一個參數,para作為第二個參數
ChangeStateWithPara(state,para){
// 自增1
state.num += para;
}
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state
state,
// 導出mutations
mutations
})
// 導出創(chuàng)建的store對象
export default store;test.vue修改如下:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
</div>
</template>
<script>
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
}
}
}
</script>效果:

3、Getter
有時候我們需要從store中的state中派生出一些狀態(tài),例如根據num的值返回基數或者偶數。如果有多個組件需要用到這個狀態(tài),那么我們就需要在每個組件里面都定義重復的一個函數,或者是定義成一個公共的函數,然后在多個組件里面導入,這兩種方式無論采用哪種都不是很理想的。這時候我們就需要用到getter了。官網解釋如下:
Vuex 允許我們在 store 中定義“getter”(可以認為是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算。同樣,Getter接受state作為第一個參數??聪旅娴睦樱?/p>
修改index.js文件如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 改變狀態(tài),通過commit
var mutations={
// state作為第一個參數
ChangeState(state){
// 自增1
state.num++;
},
// state作為第一個參數,para作為第二個參數
ChangeStateWithPara(state,para){
// 自增1
state.num += para;
}
}
// Getter,相當于store里面的計算屬性
var getters={
IsOddOrEven(state){
return state.num % 2==0?'偶數':'奇數'
}
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state
state,
// 導出mutations
mutations,
// 導出getter
getters
})
// 導出創(chuàng)建的store對象
export default store;那么該如何訪問getters呢?可以通過下面的幾種方式進行訪問:
3.1、通過屬性進行訪問
修改test.vue如下:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
<!--通過屬性訪問getters-->
<p>通過屬性訪問getters:{{$store.getters.IsOddOrEven}}</p>
</div>
</template>
<script>
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
}
}
}
</script>效果:

3.2、通過輔助函數進行訪問
vuex中提供了mapGetters輔助函數,mapGetters輔助函數僅僅是將store中的getter映射到局部計算屬性:
修改text.vue如下:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
<!--通過屬性訪問getters-->
<p>通過屬性訪問getters:{{$store.getters.IsOddOrEven}}</p>
<!--通過輔助函數訪問getters-->
<p>通過輔助函數訪問getters:{{IsOddOrEven}}</p>
</div>
</template>
<script>
// 導入輔助函數
import {mapGetters} from 'vuex'
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
},
// 使用對象展開運算符將 getter 混入 computed 對象中,數組里面是在getters里面定義的方法名稱,如果有多個
// 則在數組里面添加多個即可
...mapGetters(['IsOddOrEven'])
}
}
</script>效果:

可以看出:通過屬性訪問和通過輔助函數訪問實現的效果是一樣的。
4、Action
Vuex官網對Action的解釋如下:
Action類似于Mutation,兩者的不同之處在于:
- Action提交的是Mutation,而不是直接變更狀態(tài)。也就是說Action是用來管理Mutation的,執(zhí)行順序是先執(zhí)行Action,然后通過Action來觸發(fā)Mutation,最后在通過Mutation來變更狀態(tài)。
- Action中可以包含異步操作,Mutation中只能包含同步操作。
看下面的例子:
修改index.js如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 改變狀態(tài),通過commit
var mutations={
// state作為第一個參數
ChangeState(state){
// 自增1
state.num++;
},
// state作為第一個參數,para作為第二個參數
ChangeStateWithPara(state,para){
// 自增1
state.num += para;
}
}
// Getter,相當于store里面的計算屬性
var getters={
IsOddOrEven(state){
return state.num % 2==0?'偶數':'奇數'
}
}
// 用來管理mutations
var actions={
ExecChangeState({commit}){
// 執(zhí)行mutations里面定義的ChangeState方法
commit('ChangeState');
}
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state
state,
// 導出mutations
mutations,
// 導出getter
getters,
// 導出actions
actions
})
// 導出創(chuàng)建的store對象
export default store;4.1、通過dispatch觸發(fā)
修改test.vue如下:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
<!--通過屬性訪問getters-->
<p>通過屬性訪問getters:{{$store.getters.IsOddOrEven}}</p>
<!--通過輔助函數訪問getters-->
<p>通過輔助函數訪問getters:{{IsOddOrEven}}</p>
<!--演示Action-->
<div>
<p>屬性值:{{count}}</p>
<button @click="add">通過action改變屬性值</button>
</div>
</div>
</template>
<script>
// 導入輔助函數
import {mapGetters} from 'vuex'
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
},
// 使用對象展開運算符將 getter 混入 computed 對象中,數組里面是在getters里面定義的方法名稱,如果有多個
// 則在數組里面添加多個即可
...mapGetters(['IsOddOrEven'])
},
methods:{
add(){
// 通過dispatch觸發(fā)actions里面的ExecChangeState
this.$store.dispatch('ExecChangeState');
}
}
}
</script>效果:

下面看一下執(zhí)行順序:
分別在add()、ExecChangeState()、ChangeState()里面添加一句console.log()來查看執(zhí)行順序:
index.js修改如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 改變狀態(tài),通過commit
var mutations={
// state作為第一個參數
ChangeState(state){
console.log("mutations");
// 自增1
state.num++;
},
// state作為第一個參數,para作為第二個參數
ChangeStateWithPara(state,para){
// 自增1
state.num += para;
}
}
// Getter,相當于store里面的計算屬性
var getters={
IsOddOrEven(state){
return state.num % 2==0?'偶數':'奇數'
}
}
// 用來管理mutations
var actions={
ExecChangeState({commit}){
// 執(zhí)行mutations里面定義的ChangeState方法
console.log("actions");
commit('ChangeState');
}
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state
state,
// 導出mutations
mutations,
// 導出getter
getters,
// 導出actions
actions
})
// 導出創(chuàng)建的store對象
export default store;test.vue修改如下:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
<!--通過屬性訪問getters-->
<p>通過屬性訪問getters:{{$store.getters.IsOddOrEven}}</p>
<!--通過輔助函數訪問getters-->
<p>通過輔助函數訪問getters:{{IsOddOrEven}}</p>
<!--演示Action-->
<div>
<p>屬性值:{{count}}</p>
<button @click="add">通過action改變屬性值</button>
</div>
</div>
</template>
<script>
// 導入輔助函數
import {mapGetters} from 'vuex'
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
},
// 使用對象展開運算符將 getter 混入 computed 對象中,數組里面是在getters里面定義的方法名稱,如果有多個
// 則在數組里面添加多個即可
...mapGetters(['IsOddOrEven'])
},
methods:{
add(){
// 通過dispatch觸發(fā)actions里面的ExecChangeState
console.log("觸發(fā)add()");
this.$store.dispatch('ExecChangeState');
}
}
}
</script>效果:

從上圖可以看出執(zhí)行順序是:先執(zhí)行組件中的方法,然后執(zhí)行actions,最后在執(zhí)行mutations,最終通過mutations更新狀態(tài)。
4.2、通過輔助函數觸發(fā)
除了使用dispatch()觸發(fā)actions以外,還可以使用輔助函數mapActions,輔助函數mapActions將組件中的methods映射為store.dispatch。修改test.vue如下:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
<!--通過屬性訪問getters-->
<p>通過屬性訪問getters:{{$store.getters.IsOddOrEven}}</p>
<!--通過輔助函數訪問getters-->
<p>通過輔助函數訪問getters:{{IsOddOrEven}}</p>
<!--演示Action-->
<div>
<p>屬性值:{{count}}</p>
<button @click="add">通過action改變屬性值</button>
</div>
<!--通過輔助函數觸發(fā)actions-->
<div>
<p>屬性值:{{count}}</p>
<button @click="ExecChangeState">通過輔助函數改變屬性值</button>
</div>
</div>
</template>
<script>
// 導入輔助函數
import {mapGetters, mapActions} from 'vuex'
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
},
// 使用對象展開運算符將 getter 混入 computed 對象中,數組里面是在getters里面定義的方法名稱,如果有多個
// 則在數組里面添加多個即可
...mapGetters(['IsOddOrEven'])
},
methods:{
add(){
// 通過dispatch觸發(fā)actions里面的ExecChangeState
console.log("觸發(fā)add()");
this.$store.dispatch('ExecChangeState');
},
// 輔助函數 mapActions輔助函數將組件中的methods映射為store.dispatch
// 這里表示將'this.ExecChangeState'映射為'this.$store.dispatch('ExecChangeState')'
...mapActions(['ExecChangeState'])
}
}
</script>效果:

到了這里,可能有人會問:通過actions的方式進行管理mutations比直接使用mutations更復雜了,為什么還要這么做呢?實際上并非如此,mutations只能執(zhí)行同步方法,Action就不受此限制,我們可以在actions內部執(zhí)行異步方法,看下面的例子:
修改index.js,添加異步方法,代碼如下:
// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from 'vuex'
// 添加全局引用
Vue.use(Vuex);
// 創(chuàng)建state
const state={
// 定義num屬性并賦初始值為1
num:1
}
// 改變狀態(tài),通過commit
var mutations={
// state作為第一個參數
ChangeState(state){
console.log("mutations");
// 自增1
state.num++;
},
// state作為第一個參數,para作為第二個參數
ChangeStateWithPara(state,para){
// 自增1
state.num += para;
}
}
// Getter,相當于store里面的計算屬性
var getters={
IsOddOrEven(state){
return state.num % 2==0?'偶數':'奇數'
}
}
// 用來管理mutations
var actions={
ExecChangeState({commit}){
// 執(zhí)行mutations里面定義的ChangeState方法
console.log("actions");
commit('ChangeState');
},
// 執(zhí)行異步方法:點擊按鈕5秒之后再改變屬性的值
ExecChangeStateAsync({commit}){
setTimeout(() => {
commit('ChangeState')
}, 5000);
}
}
// 創(chuàng)建store對象
const store=new Vuex.Store({
// 添加state
state,
// 導出mutations
mutations,
// 導出getter
getters,
// 導出actions
actions
})
// 導出創(chuàng)建的store對象
export default store;修改test.vue文件:
<template>
<div>
<p>初始值:{{this.$store.state.num}}</p>
<p>通過計算屬性獲取初始值:{{count}}</p>
<button @click="$store.commit('ChangeState')">改變屬性值</button>
<button @click="$store.commit('ChangeStateWithPara',5)">傳遞參數改變屬性值</button>
<!--通過屬性訪問getters-->
<p>通過屬性訪問getters:{{$store.getters.IsOddOrEven}}</p>
<!--通過輔助函數訪問getters-->
<p>通過輔助函數訪問getters:{{IsOddOrEven}}</p>
<!--演示Action-->
<div>
<p>屬性值:{{count}}</p>
<button @click="add">通過action改變屬性值</button>
</div>
<!--通過輔助函數觸發(fā)actions-->
<div>
<p>屬性值:{{count}}</p>
<button @click="ExecChangeState">通過輔助函數改變屬性值</button>
</div>
<!--執(zhí)行異步actions-->
<div>
<p>屬性值:{{count}}</p>
<button @click="ExecChangeStateAsync">通過異步方法改變屬性值</button>
</div>
</div>
</template>
<script>
// 導入輔助函數
import {mapGetters, mapActions} from 'vuex'
export default {
name:'test',
// 計算屬性
computed:{
count(){
return this.$store.state.num;
},
// 使用對象展開運算符將 getter 混入 computed 對象中,數組里面是在getters里面定義的方法名稱,如果有多個
// 則在數組里面添加多個即可
...mapGetters(['IsOddOrEven'])
},
methods:{
add(){
// 通過dispatch觸發(fā)actions里面的ExecChangeState
console.log("觸發(fā)add()");
this.$store.dispatch('ExecChangeState');
},
// 輔助函數 mapActions輔助函數將組件中的methods映射為store.dispatch
// 這里表示將'this.ExecChangeState'映射為'this.$store.dispatch('ExecChangeState')'
// 這里表示將'this.ExecChangeStateAsync'映射為'this.$store.dispatch('ExecChangeStateAsync')'
...mapActions(['ExecChangeState','ExecChangeStateAsync'])
}
}
</script>效果:

5、Module
由于使用單一狀態(tài)樹,應用的所有狀態(tài)會集中到一個比較大的對象。當應用變得非常復雜時,store對象就有可能變得相當臃腫。為了解決上面的問題,Vuex允許我們將store分割成模塊(Module)。每個模塊都有自己的state、mutation、action、getter。
五、總結
通過上面的基本講解,了解了Vuex的一些基本用法,Vuex在進行狀態(tài)管理方面很方便,但并不是說Vue項目中就一定要使用Vuex,在Vuex中也可以不使用Vuex。下面總結一下Vuex中的核心概念和底層原理
1、核心概念
- State:this.$store.state.xxx 取值
- Getter:this.$store.getters.xxx 取值
- Mutation:this.$store.commit('xxx') 賦值改變狀態(tài),只能調用同步方法
- Action:this.$store.dispatch('xxx') 賦值,不能直接更改狀態(tài),最后還是要通過commit更改狀態(tài),可以操作異步方法。
- Module:模塊
2、底層原理
- State:提供一個響應式數據;
- Getter:借助Vue的計算屬性computed來實現緩存;
- Mutation:更改State狀態(tài);
- Action:觸發(fā)mutation方法;
- Module:Vue.set動態(tài)添加state到響應式數據中;
到此這篇關于Vuex詳細介紹和使用方法的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Vue3的ts報錯:類型"{}"上不存在屬性"xxx"的兩種徹底根治解決方法
這篇文章主要給大家介紹了關于Vue3的ts報錯:類型"{}"上不存在屬性"xxx"的兩種徹底根治解決方法,這是最近做項目中遇到的一個問題,這里給大家總結下解決辦法,需要的朋友可以參考下2023-08-08
vue+elementui實現動態(tài)添加行/可編輯的table
這篇文章主要為大家詳細介紹了vue+elementui實現動態(tài)添加行/可編輯的table,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
vue路由跳轉router-link清除歷史記錄的三種方式(總結)
這篇文章主要介紹了vue路由跳轉router-link清除歷史記錄的三種方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04

