摘要:簡單來說,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>
看,一樣的效果: