Vue系列之—Vuex詳解


一、Vuex概述

1.1 官方解釋

Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。

  • 它采用 集中式存儲管理 應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化 -Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel調試、狀態快照導入導出等高級調試功能。

在這里插入圖片描述

1.2 個人理解

狀態管理模式、集中式存儲管理這些名詞聽起來就非常高大上,讓人有點捉摸不透。

其實,可以簡單的將其看成把需要多個組件共享的變量全部存儲在一個對象里面。

然后,將這個對象放在頂層的Vue實例中,讓其他組件可以使用。

那么,多個組件是不是就可以共享這個對象中的所有變量屬性了呢?

如果是這樣的話,為什么官方還要專門出一個插件Vuex呢?難道我們不能自己封裝一個對象來管理嗎?

當然可以,只是我們要先想想Vue.js帶給我們最大的便利是什么呢?沒錯,就是響應式。

如果你自己封裝實現一個對象能不能保證它里面所有的屬性做到響應式呢?當然也可以,只是自己封裝可能稍微麻煩一些。

Vuex就是為了提供這樣一個在多個組件間共享狀態的插件。

1.3 組件間共享數據的方式

  • 父向子傳值:v-bind屬性綁定

  • 子向父傳值:v-on事件綁定

  • 兄弟組件之間共享數據:EventBus

  • $on 接收數據的組件

  • $emit 發送數據的組件

上述只適合小范圍內數據共享,如果是復雜應用,就不再合適。

1.4 Vuex是什么

Vuex是實現組件全局狀態(數據)管理的一種機制,可以方便的實現組件之間數據的共享

如圖:

在不使用Vuex進行狀態管理時,如果要從最下面的紫色組件傳遞數據的話,還是比較繁瑣,也不便於維護。

在使用Vuex進行狀態管理時,只需要一個共享Store組件,紫色組件將數據寫入Store中,其他使用的組件直接從Store中讀取即可。 在這里插入圖片描述

1.5 使用Vuex統一管理好處

  • 能夠在Vuex中集中管理共享的數據,易於開發和后期維護

  • 能夠高效地實現組件之間的數據共享,提高開發效率

  • 存儲在Vuex中的數據都是響應式的,能夠實時保持數據與頁面的同步

二、狀態管理

2.1 單頁面狀態管理

我們知道,要在單個組件中進行狀態管理是一件非常簡單的事情,如圖:

在這里插入圖片描述

  • State:指的就是我們的狀態,可以暫時理解為組件中data中的屬性

  • View:視圖層,可以針對State的變化, 顯示不同的信息

  • Actions:這里的Actions主要是用戶的各種操作,如點擊、輸入等,會導致狀態發生變化

簡單加減法案例,代碼如下:

<template>
  <div>
    <div>當前計數為:{{counter}}</div>
    <button @click="counter+=1">+1</button>
    <button @click="counter-=1">-1</button>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      counter: 0
    };
  }
};
</script>

在這個案例中,有沒有狀態需要管理呢?肯定是有的,就是個數counter counter需要某種方式被記錄下來,也就是上述中的的State部分 counter的值需要被顯示在潔面皂,這個就是上述中的View部分 界面發生某些操作(比如此時的+1、-1),需要去更新狀態,這就是上述中的Actions部分 這就是一個最基本的單頁面狀態管理。

2.2 多頁面狀態管理

Vue已經幫我們做好了單個界面的狀態管理,但是如果是多個界面呢,比如:多個視圖View都依賴同一個狀態(一個狀態改了,多個界面需要進行更新) 不同界面的Actions都想修改同一個狀態 也就是說對於某些狀態(狀態1/狀態2/狀態3)來說只屬於我們某一個視圖,但是也有一些狀態(狀態a/狀態b/狀態c)屬於多個試圖共同想要維護的,那怎么辦呢?

狀態1/狀態2/狀態3你放在自己的組件中,自己管理自己用,沒問題 但是狀態a/狀態b/狀態c我們希望交給一個大管家來統一幫助我們管理 沒錯,Vuex就是為我們提供這個大管家的工具。

2.3 全局單例模式

我們現在要做的就是將共享的狀態抽出來,交給我們的大管家,統一進行管理,每個視圖按照規定,進行訪問和修改操作。

這就是Vuex的基本思想

2.4 管理哪些狀態

如果你做過大型開放,你一定遇到過多個狀態,在多個界面間的共享問題。

比如用戶的登錄狀態、用戶名稱、頭像、地理位置信息等 比如商品的收藏、購物車中的物品等 這些狀態信息,我們都可以放在統一放在Vuex中,對它進行保存和管理,而且它們還是響應式的。

一般情況下,只有組件之間共享的數據,才有必要存儲到Vuex中。
對於組件中的私有數據,依舊存儲在組件自身的data中即可。

三、Vuex的基本使用

3.1 安裝

npm install vuex --save

3.2 導入

import Vuex from 'vuex'
Vue.use(Vuex)

3.3 創建store對象

const store = new Vuex.Store({
    // state中存放的就是全局共享數據 
    state:{     
    count: 0 
    } 
})

3.4 掛載store對象

new Vue({ 
    el: '#app', 
    render: h=>h(app)m 
    router, 
    //將創建的共享數據對象,掛載到Vue實例中
    //所有的組件,就可以直接從store中獲取全局的數據了
    store 
})

3.5 創建帶vuex的vue項目

3.5.1 創建過程

  1. 打開終端,輸入命令:vue ui

  2. 當項目儀表盤打開之后,我們點擊頁面左上角的項目管理下拉列表,再點擊Vue項目管理器

  3. 點擊創建項目

  4. 設置項目名稱和包管理器

  5. 設置手動配置項目

  6. 設置功能項

  7. 創建項目

3.5.2 項目代碼格式化

在項目根目錄(與src平級)中創建 .prettierrc 文件,編寫代碼如下:

{
    "semi":false,
    "singleQuote":true
}

四、Vuex的核心概念

4.1 State

4.1.1 概念

State是提供唯一的公共數據源,所有共享的數據都要統一放到`Store的State中進行存儲。

如果狀態信息是保存到多個Store對象中的,那么之后的管理和維護等都會變得特別困難,所以Vuex也使用了單一狀態樹(單一數據源Single Source of Truth)來管理應用層級的全部狀態。

單一狀態樹能夠讓我們最直接的方式找到某個狀態的片段,而且在之后的維護和調試過程中,也可以非常方便的管理和維護。

export default new Vuex.Store({  
    state: {    
        count: 0  
    }
}

4.1.2 State數據訪問方式一

通過this.$store.state.全局數據名稱訪問,eg.

<h3>當前最新Count值為:{{this.$store.state.count}}</h3>

4.1.3 State數據訪問方式二

vuex中按需導入mapState函數

import { mapState } from 'vuex'

通過剛才導入的mapState函數,將當前組件需要的全局數據,映射為當前組件的computed計算屬性:

<template>
  <div>
    <h3>當前最新Count值為:{{ count }}</h3>
    <button>-1</button>
  </div>
</template>
<script>
import { mapState } from "vuex";
​
export default {
  computed: {
    ...mapState(["count"])
  }
};
</script>

4.2 Mutation

4.2.1 引入

如果想修改count的值,要怎么做呢?

也許聰明的你,已經想到,直接在組件中對this.$store.state.count進行操作即可,代碼如下

<template>
  <div>
    <h3>當前最新Count值為:{{this.$store.state.count}}</h3>
    <button @click="add">+1</button>
  </div>
</template>
<script>
export default {
  methods: {
    add() {
      this.$store.state.count++;
    }
  }
};
</script>

測試發現,這可以實現需求,完成+1操作。

但是,這種方法在vuex中是嚴格禁止的,那要怎么做呢?這時,就需要使用Mutation了。

4.2.2 概念

Mutation用於變更存儲在Store中的數據。

  • 只能通過mutation變更Store數據,不可以直接操作Store中的數據

  • 通過這種方式,雖然操作稍微繁瑣一些,但可以集中監控所有數據的變化,直接操作Store數據是無法進行監控的

4.2.3 定義Mutation函數

mutations中定義函數,如下:

 mutations: {    
      // 自增    
      add(state) {      
      state.count++    
      }  
  }

定義的函數會有一個默認參數state,這個就是存儲在Store中的state對象。

4.2.4 調用Mutation函數

Mutation中不可以執行異步操作,如需異步,請在Action中處理

4.2.4.1 方式一

在組件中,通過this.$store.commit(方法名)完成觸發,如下:

 mutations: {    
      // 自增    
      add(state) {      
      state.count++    
      }  
  }
4.2.4.2 方式二

在組件中導入mapMutations函數

import { mapMutations } from 'vuex'

通過剛才導入的mapMutations函數,將需要的mutations函數映射為當前組件的methods方法:

methods:{
    ...mapMutations('add','addN'),
    // 當前組件設置的click方法
    addCount(){
        this.add()
    }
}

4.2.5 Mutation傳遞參數

在通過mutation更新數據的時候,有時候需攜帶一些額外的參數,此處,參數被成為mutation的載荷Payload

如果僅有一個參數時,那payload對應的就是這個參數值,eg.

在這里插入圖片描述

如果是多參數的話,那就會以對象的形式傳遞,此時的payload是一個對象,可以從對象中取出相關的數據。

mutations中定義函數時,同樣可以接收參數,示例如下:

mutations: {
    // 自增
    add(state) {
      state.count++
    },
    // 帶參數
    addNum(state, payload) {
      state.count += payload.number
    }
  }

在組件中,調用如下:

methods: {
  add() {
    //   this.$store.state.count++;
    this.$store.commit("add");
  },
  addNum() {
    this.$store.commit("addNum", {
      number: 10
    });
  }
}

4.2.6 Mutation響應規則

Vuexstore中的State是響應式的,當State中的數據發生改變時,Vue組件也會自動更新。

這就要求我們必須遵守一些Vuex對應的規則:

  • 提前在store中初始化好所需的屬性

  • 當給 State 中的對象添加新屬性時,使用如下方式:

    • 使用Vue.set(obj,'newProp','propValue')

    • 用新對象給舊對象重新賦值

    示例代碼:

      updateUserInfo(state) {
          // 方式一
          Vue.set('user', 'address', '北京市')
          // 方式二
          state.user = {
            ...state.user,
            'address': '上海市'
          }
        }

4.2.7 Mutation常量類型

4.2.7.1 引入

思考一個問題:

在mutation中, 我們定義了很多事件類型(也就是其中的方法名稱),當項目越來越大時,Vuex管理的狀態越來越多,需要更新狀態的情況也越來越多,也就意味着Mutation中的方法越來越多。

當方法過多,使用者需要花費大量時間精力去記住這些方法,甚至多個文件間來回切換,查看方法名稱,也存在拷貝或拼寫錯誤的情況。

那么該如何避免呢?

  • 在各種Flux實現中,一種很常見的方案就是使用常量替代Mutation事件的類型

  • 可以將這些常量放在一個單獨的文件中,方便管理,整個App所有的事件類型一目了然

4.2.7.1 解決方案
  • 創建mutation-types.js文件,在其中定義常量

  • 定義常量時, 可以使用ES2015中的風格, 使用一個常量來作為函數的名稱

  • 使用處引入文件即可

新建mutation-types.js

在這里插入圖片描述

store/index.js中引入並使用:

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    user: {
      name: '旺財',
      age: 12
    }
  },
  mutations: {
    // 自增
    [types.ADD_NUM](state) {
      state.count++
    },
}

在組件中,引入並調用:

<script>
import { ADD_NUM } from "../store/mutation-type";
export default {
  methods: {
    add() {
      this.$store.commit(ADD_NUM);
      //   this.addAsync();
      //   this.$store.state.count++;
      //   this.$store.commit("add");
    }
  }
};
</script>

4.3 Action

Action類似於Mutation,但是是用於處理異步任務的,比如網絡請求等

如果通過異步操作變更數據,必須通過Action,而不能使用Mutation,但在Action中還是要通過觸發Mutation的方式間接變更數據。

4.3.1 參數context

actions中定義的方法,都會有默認值context

  • context是和store對象具有相同方法和屬性的對象

  • 可以通過context進行commit相關操作,可以獲取context.state數據

但他們並不是同一個對象,在Modules中會介紹到區別。

4.3.2 使用方式一

index.js中,添加actions及對應的方法:

export default new Vuex.Store({
  state: {
    count: 0
  },
 //只有 mutations 中定義的函數,才有權力修改 state 中的數據
  mutations: {
    // 自增
    add(state) {
      state.count++
    }
  },
  actions: {
    addAsync(context) {
      setTimeout(() => {
      //在 action 中,不能直接修改 state 中的數據
      //必須通過 context.commit() 觸發某個 mutation 才行
        context.commit('add')
      }, 1000);
    }
  }
})

在組件中調用:

<script>
export default {
  methods: {
    addNumSync(){
        // dispatch函數 專門用於觸發 Action
        this.$store.dispatch('addAsync')
    }
  }
};
</script>

4.3.3 使用方式二

在組件中,導入mapActions函數

import { mapActions } from 'vuex'

通過剛才導入的mapActions函數,將需要的actions函數映射為當前組件的methods方法:

<script>
import { mapActions } from "vuex";
export default {
  methods: {
    ...mapActions(["addAsync"]),
    add() {Î
        this.addAsync()
    },
}

4.3.4 使用方式三

在導入mapActions后,可以直接將指定方法綁定在@click事件上。

...mapActions(["addAsync"]),
---------------------------
 <button @click="addAsync">+1(異步)</button>

該方式也適用於導入的mapMutations

4.3.5 Actions攜帶參數

index.jsactions中,增加攜帶參數方法,如下:

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    // 帶參數
    addNum(state, payload) {
      state.count += payload.number
    }
  },
  actions: {
    addAsyncParams(context, payload) {
      setTimeout(() => {
        context.commit('addNum', payload)
      }, 1000);
    }
  }
})

在組件中,調用如下:

methods: {
    addNumSyncParams() {
      this.$store.dispatch("addAsyncParams", {
        number: 100
      });
    }
  }

4.3.6 Actions與Promise結合

Promise經常用於異步操作,在Action中,可以將異步操作放在Promise中,並且在成功或失敗后,調用對應的resolvereject

示例:

store/index.js中,為actions添加異步方法:

actions: {
    loadUserInfo(context){
      return new Promise((resolve)=>{
        setTimeout(() => {
          context.commit('add')
          resolve()
        }, 2000);
      })
    }
  }

在組件中調用,如下:

methods: {
    addPromise() {
      this.$store.dispatch("loadUserInfo").then(res => {
        console.log("done");
      });
    }
}

4.4 Getter

  • Getters用於對Store中的數據進行加工處理形成新的數據,類似於Vue中的計算屬性

  • Store中數據發生變化,Getters的數據也會跟隨變化

4.4.1 使用方式一

index.js中定義getter

//定義 Getter
const store = new Vuex.Store({
    state:{
    count: 0
    },
    getters:{
        showNum(state){
          return '當前Count值為:['+state.count']'
        }
      }
})

在組件中使用:

this.$store.getters.名稱
<h3>{{ this.$store.getters.showNum }}</h3>
4.4.2 使用方式二

在組件中,導入mapGetters函數

import { mapGetters } from 'vuex'

通過剛才導入的mapGetters函數,將需要的getters函數映射為當前組件的computed方法:

  computed: {
    ...mapGetters(["showNum"])
  }

使用時,直接調用即可:

<h3>{{ showNum }}</h3>

4.5 Modules

4.5.1 概念

Module是模塊的意思,為什么會在Vuex中使用模塊呢?

  • Vues使用單一狀態樹,意味着很多狀態都會交給Vuex來管理

  • 當應用變的非常復雜時,Store對象就可能變的相當臃腫

  • 為解決這個問題,Vuex允許我們將store分割成模塊(Module),並且每個模塊擁有自己的State、Mutation、Actions、Getters

4.5.2 使用

store目錄下,新建文件夾modules,用於存放各個模塊的modules文件,此處以moduleA為例。

modules文件夾中,新建moduleA.js,內部各屬性statemutations等都和之前一致,注釋詳見代碼,示例如下:

export default {
    state: {
        name: '鳳凰於飛'
    },
    actions: {
        aUpdateName(context) {
            setTimeout(() => {
                context.commit('updateName', '旺財')
            }, 1000);
        }
    },
    mutations: {
        updateName(state, payload) {
            state.name = payload
        }
    },
    getters: {
        fullName(state) {
            return state.name + '王昭君'
        },
        fullName2(state, getters) {
            // 通過getters調用本組方法
            return getters.fullName + ' 禮拜'
        },
        fullName3(state, getters, rootState) {
            // state代表當前module數據狀態,rootState代表根節點數據狀態
            return getters.fullName2 + rootState.counter
        }
    }
}
  • 局部狀態通過context.state暴露出來,根節點狀態則為context.rootState

store/index.js中引用moduleA,如下:

import Vue from "vue"
import Vuex from "vuex"

import moduleA from './modules/moduleA'

Vue.use(Vuex)

const store = new Vuex.Store({
    modules: {
        a: moduleA
    }
})

export default store

這樣就通過分模塊完成了對狀態管理的模塊化拆分。

4.6 優化

如果項目非常復雜,除了分模塊划分外,還可以將主模塊的actionsmutationsgetters等分別獨立出去,拆分成單獨的js文件,分別通過export導出,然后再index.js中導入使用。

示例: 分別將主模塊的actionsmutationsgetters獨立成js文件並導出,以actions.js為例,

export default{
    aUpdateInfo(context, payload) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                context.commit('updateInfo')
                resolve()
            }, 1000);
        })
    }
}

store/index.js中,引入並使用,如下:

import Vue from "vue"
import Vuex from "vuex"
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'


Vue.use(Vuex)

const state = {
    counter: 1000,
    students: [
        { id: 1, name: '旺財', age: 12 },
        { id: 2, name: '小強', age: 31 },
        { id: 3, name: '大明', age: 45 },
        { id: 4, name: '狗蛋', age: 78 }
    ],
    info: {
        name: 'keko'
    }
}

const store = new Vuex.Store({
    state,
    mutations,
    getters,
    actions,
    modules: {
        a: moduleA
    }
})

export default store

最終項目目錄圖:

在這里插入圖片描述

總結

本文以自己能理解的方式詳細介紹了Vuex的概念以及核心知識點,再總結一下:

  1. Vuex主要用於管理Vue組件中共享的數據。

  2. Vuex中有state、mutation、action、getter等核心概念。

  3. 獲取state可以通過this.$store.state.xx或者是通過定義mapState來獲取。

  4. 修改state中的變量需要通過mutation函數實現,而mutation的觸發由兩種方式,一種是通過this.$store.commit()函數,另外一種就是通過mapMutations來實現。

  5. mutation只能用於修改數據,而Actions可以實現異步操作。

  6. 通過Actions的異步操作+mutation的修改數據,可以實現異步修改數據。調用Actions有兩種方式,第一種是通過this.$store.dispatch來調用,另外一種方式是通過mapActions來調用。

  7. Getters函數用於對Store中數據進行加工,不會修改原本Store中的數據;Getters中的數據會受Store中數據進行影響。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM