一文教會你認識Vuex狀態機
摘要:簡單來說,Vuex就是實現組件全局狀態(數據)管理的一種機制,可以方便的實現組件之間數據的共享。
本文分享自華為雲社區《Vuex狀態機快速了解與應用》,原文作者:北極光之夜。
一. 速識概念:
1. 組件之間共享數據的方式:
通常有以下幾種方式:
- 父向子傳值:v-bind 屬性綁定;
- 子向父傳值:v-on 事件綁定;
- 兄弟組件之間共享數據:EventBus;
2. vuex是什麼:
- 按照官方的話來說,Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension (opens new window),提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。
- 簡單來說,Vuex就是實現組件全局狀態(數據)管理的一種機制,可以方便的實現組件之間數據的共享。
3.使用vuex優點:
- 能夠在vuex中集中管理共享的數據,易於開發和後期維護。
- 能夠高效地實現組件之間的數據共享, 提高開發效率。
- 存儲在vuex中的數據都是響應式的,能夠實時保持數據與頁面的同步。
- 解決了非父子組件的消息傳遞(將數據存放在state中)。
- 減少了AJAX請求次數,有些情景可以直接從記憶體中的state獲取。
一般情況下,只有組件之間共享的數據,才有必要存儲到vuex中。而對於組件中的私有數據,就沒必要了,依舊存儲在組件自身的data中即可。當然,如果你想要都存在vuex中也是可以的。
二. 基本使用:
1.安裝依賴包:
npm install vuex --save
2.導入依賴包:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
3.創建store對象:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ //state中存放的就是全局共享的數據 state: { count: 0 } })
4. 將store對象掛載到vue實例中:
new Vue({ el: '#app', store })
此時所有組件就可以從store中獲取數據了。
三.創建項目:
下面為創建一個vue項目流程,後面會有案例:
(1)打開cmd窗口輸入 vue ui 打開vue的可視化面板:
(2)選擇新建項目路徑:
(3)命名:
(4)手動選擇配置,注意用的是vue2版本:
(5)創建:
(6)下一步:
(7)創建成功,到對應目錄打開vscode開始編程:
(8)運行項目:
四. 講解前提:
前提(注意):
寫一個計數器小案例,從案例中配合概念能更快上手vuex。所以下面核心概念中的程式碼部分是基於這個小案例來演示的。目標:寫兩個子組件,有一個公共count值,在父組件中,其中一個組件實現點擊後count值減1,一個組件實現點擊後count值增1。
父組件 App.vue 初始程式碼:
<template> <div id="app"> <my-add></my-add> <p>--------------------</p> <my-reduce></my-reduce> </div> </template> <script> // 引入組件 import Add from './components/Add.vue' import Reduce from './components/Reduce.vue' export default { name: 'App', data() { return { } }, components: { 'my-add': Add, 'my-reduce': Reduce } } </script>
子組件Add.vue初始程式碼:
<template> <div> <p>count值為:</p> <button>+1</button> </div> </template> <script> export default{ data() { return { } }, } </script>
子組件Reduce.vue初始程式碼:
<template> <div> <p>count值為:</p> <button>-1</button> </div> </template> <script> export default{ data() { return { } }, } </script>
store對象初始程式碼為:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { count: 0 } })
初始效果:
五.核心概念:
1.state:
按照官方的話來說,如下:Vuex 使用單一狀態樹——是的,用一個對象就包含了全部的應用層級狀態。至此它便作為一個「唯一數據源 (SSOT)」而存在。這也意味著,每個應用將僅僅包含一個 store 實例。
簡單來說,就是State提供唯一的公共數據源, 所有共享的數據都要統一放到Store的State中進行存儲。
1.1 組件中訪問state的第一種方式:
組件中直接輸入以下命令:
如在Add.vue子組件中引用:
<template> <div> <p>count值為:{{this.$store.state.count}}</p> <button>+1</button> </div> </template> //下面部分程式碼跟前面一樣無改變,所以省略了
看效果,顯示了count的值為0:
1.2 組件中訪問state的第二種方式:
(1)從 vuex 中按需導入 mapState 函數
import { mapState } from 'vuex'
(2)通過剛才導入的mapState函數,將當前組件需要的全局數據,映射為當前組件的computed計算屬性:
computed: {
...mapState([count])
}
小知識:computed用來監控自己定義的變數,該變數不在data裡面聲明,直接在computed裡面定義,然後就可以在頁面上進行雙向數據綁定展示出結果或者用作其他處理;
如在Reduce.vue子組件中引用:
<template> <div> <p>count值為:{{count}}</p> <button>-1</button> </div> </template> <script> import {mapState} from 'vuex' export default{ data() { return { } }, computed: { ...mapState(['count']) } } </script>
看效果,同樣顯示了count的值為0:
2. mutation:
按照官方的話來說,更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字元串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方,並且它會接受 state 作為第一個參數。
簡單來說就是Mutation用於變更Store中的數據。
①只能通過mutation變更Store數據,不可以直接操作Store中的數據。
②通過這種方式雖然操作起來稍微繁瑣一些,但是可以集中監控所有數據的變化。
比如,要實現count值自增加1的操作,那就在先motations里定義一個自增加1的函數。然後對應子組件想用,該組件就直接引入mutation並調用對應的函數就好。
如下,Add.vue子組件要實現自增加1功能:先在狀態機里的mutations里定義一個能實現自增的函數add:
export default new Vuex.Store({ state: { count: 0 }, mutations: { //自增加1函數 add(state){ state.count++ } } })
2.1 觸發mutation的第一種方式:
Add.vue子組件里給按鈕綁定點擊事件,並觸發mutation:
<template> <div> <p>count值為:{{this.$store.state.count}}</p> <button @click="btnAdd">+1</button> </div> </template> <script> export default{ data() { return { } }, methods: { btnAdd() { // 第一種引入mutation的方式,觸發add函數 this.$store.commit('add') } } } </script>
看效果實現了點擊自增:
2.2 觸發mutation並傳參數:
當然,當組件里調用mutation里函數時,也是可以傳參數的。比如,有一個自增函數,但增多少看調用時傳入的參數:
export default new Vuex.Store({ state: { count: 0 }, mutations: { // 傳入參數,第一個一定是state,第二個為傳入的參數 //自增加 n 的函數 addN(state,n){ state.count+= n } } })
對應組件調用時要傳入參數:
methods: { btnAdd2() { // 引入mutation的方式,觸發addN函數 // 並傳參,自增加6吧 this.$store.commit('addN',6) } }
2.3 觸發mutation的第二種方式:
(1)從 vuex 中按需導入 mapMutations 函數
import { mapMutations } from 'vuex'
(2)通過剛才導入的mapMutations函數,將需要的mutations函數,映射為當前組件的methods方法:
methods: { ...mapMutations(['add','addN']) }
實戰,實現Reduce.vue組件的點擊自減1的功能要求:
狀態機添加自減函數:
export default new Vuex.Store({ state: { count: 0 }, mutations: { //自增加1函數 add(state){ state.count++ }, // 自減1的函數 sub(state){ state.count-- } } })
Reduce.vue組件點擊按鈕實現自減1:
<template> <div> <p>count值為:{{count}}</p> <button @click="btnSub">-1</button> </div> </template> <script> //導入 import {mapState,mapMutations} from 'vuex' export default{ data() { return { } }, computed: { ...mapState(['count']) }, methods: { // 映射mutation里的sub函數 ...mapMutations(['sub']), // 要自減,調用sub函數 btnSub(){ this.sub() } } } </script>
看效果:
3.Action:
至此,第四大點裡的案例已經完成,已經實現了自增和自減,現在對案例做改進,要我們點擊按鈕一秒後再自增和自減,該怎麼實現?可以在狀態機里的mutation里的函數是加一個1秒定時器嗎,這肯定是不行的,因為mutation里不支援非同步操作,那咋辦,噹噹當,Action閃亮登場。
Action 可以包含任意非同步操作,所以它用來處理非同步任務。
Action 提交的是 mutation,而不是直接變更狀態。記住它並不能直接修改state里的數據,只有mutation能修改。就是說,如果通過非同步操作變更數據,必須通過Action,而不能使用Mutation,但是在Action中還是要通過觸發Mutation的方式間接變更數據。
先在狀態機里定義Action:
export default new Vuex.Store({ state: { count: 0 }, mutations: { //自增加1函數 add(state){ state.count++ }, // 自減1的函數 sub(state){ state.count-- } }, // 定義action,裡面的addAsync函數實現1秒後執行mutation里的add函數 actions: { addAsync(context) { setTimeout(()=>{ // 必須通過context.commit()觸發mutation才行 context.commit('add') },1000) } } })
Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit 提交一個 mutation。
3.1 觸發Action的第一種方式:
更改組件Add.vue程式碼,引入Action,實現非同步自增操作。
<template> <div> <p>count值為:{{this.$store.state.count}}</p> <button @click="btnAdd">+1</button> </div> </template> <script> export default{ data() { return { } }, methods: { btnAdd() { // 第一種引入Action的方式,觸發addAsync函數 // 這裡的dispatch專門用來調用action函數 this.$store.dispatch('addAsync') } } } </script>
看效果,實現1秒後自增:
3.2 觸發Action非同步任務並傳參數:
當然,當組件里調用action里函數時,也是可以傳參數的。比如,有一個點擊1秒後才執行的自增函數,但增多少看調用時傳入的參數:
定義:
export default new Vuex.Store({ state: { count: 0 }, mutations: { // 傳入參數,第一個一定是state,第二個為傳入的參數 //自增加 n 的函數 addN(state,n){ state.count+= n } }, actions: { // 有參數 n,這個n又傳給了mutation里的addN函數 addNAsync(context,n) { setTimeout(()=>{ context.commit('addN',n) },1000) } } })
對應組件調用時要傳入參數:
methods: { btnAdd2() { // 調用dispatch函數 // 觸發action時傳參數,為 6 吧,表示自增6 this.$store.dispatch('addNAsync',6) } }
3.3 觸發Action的第二種方式:
(1)從 vuex 中按需導入 mapActions 函數
import { mapActions } from 'vuex'
(2)通過剛才導入的mapActions函數,將需要的actions函數,映射為當前組件的methods方法:
methods: { ...mapActions(['add','addN']) }
實戰,實現Reduce.vue組件的點擊一秒後自減1的功能要求:
定義actions里的subAsync為一秒後自減函數:
export default new Vuex.Store({ state: { count: 0 }, mutations: { //自增加1函數 add(state){ state.count++ }, // 自減1的函數 sub(state){ state.count-- } }, actions: { addAsync(context) { setTimeout(()=>{ context.commit('add') },1000) }, subAsync(context) { setTimeout(()=>{ context.commit('sub') },1000) } } })
更改Reduce.vue程式碼,實現功能:
<template> <div> <p>count值為:{{count}}</p> <button @click="btnSub">-1</button> </div> </template> <script> //導入 import {mapState,mapActions} from 'vuex' export default{ data() { return { } }, computed: { ...mapState(['count']) }, methods: { // 映射Action里的函數 ...mapActions(['subAsync']), // 要自減,調用subAsync函數 btnSub(){ this.subAsync() } } } </script>
看效果:
4. Getter:
Getter用於對Store中的數據進行加工處理形成新的數據。且要注意的是它並不會修改state中的數據。
①Getter 可以對Store中已有的數據加工處理之後形成新的數據,類似Vue的計算屬性。
②Store 中數據發生變化,Getter 的數據也會跟著變化。
如,有一個返回當前count+1的getter函數:
4.1 觸發getters的第一種方式:
this.$store.getters.名稱
在App.vue組件中顯示:
<template> <div id="app"> <my-add></my-add> <p>--------------------</p> <my-reduce></my-reduce> <p>--------------------</p> <h3>{{this.$store.getters.showNum}}</h3> </div> </template>
效果:
4.2觸發getters的第二種方式:
(1)從 vuex 中按需導入 mapGetters 函數
import { mapGetters } from 'vuex'
(2)通過剛才導入的mapGetters函數,將當前組件需要的全局數據,映射為當前組件的computed計算屬性:
computed: { ...mapGetters(['showNum']) }
還是在App.vue中使用把:
<template> <div id="app"> <my-add></my-add> <p>--------------------</p> <my-reduce></my-reduce> <p>--------------------</p> <h3>{{showNum}}</h3> </div> </template> <script> // 引入組件 import Add from './components/Add.vue' import Reduce from './components/Reduce.vue' // 導入 mapGetters函數 import {mapGetters} from 'vuex' export default { name: 'App', data() { return { } }, components: { 'my-add': Add, 'my-reduce': Reduce }, // 引入 getter computed: { ...mapGetters(['showNum']) } } </script>
看,一樣的效果: