一、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 創建過程
-
打開終端,輸入命令:vue ui
-
當項目儀表盤打開之后,我們點擊頁面左上角的項目管理下拉列表,再點擊Vue項目管理器
-
點擊創建項目
-
設置項目名稱和包管理器
-
設置手動配置項目
-
設置功能項
-
創建項目
3.5.2 項目代碼格式化
在項目根目錄(與src平級)中創建 .prettierrc 文件,編寫代碼如下:
{ "semi":false, "singleQuote":true }
四、Vuex的核心概念
4.1 State
4.1.1 概念
Sta
te是提供唯一的公共數據源,所有共享的數據都要統一放到`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響應規則
Vuex
的store
中的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.js
的actions
中,增加攜帶參數方法,如下:
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
中,並且在成功或失敗后,調用對應的resolve
或reject
。
示例:
在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
,內部各屬性state
、mutations
等都和之前一致,注釋詳見代碼,示例如下:
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 優化
如果項目非常復雜,除了分模塊划分外,還可以將主模塊的actions
、mutations
、getters
等分別獨立出去,拆分成單獨的js
文件,分別通過export
導出,然后再index.js
中導入使用。
示例: 分別將主模塊的actions
、mutations
、getters
獨立成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的概念以及核心知識點,再總結一下:
-
Vuex主要用於管理Vue組件中共享的數據。
-
Vuex中有state、mutation、action、getter等核心概念。
-
獲取state可以通過this.$store.state.xx或者是通過定義mapState來獲取。
-
修改state中的變量需要通過mutation函數實現,而mutation的觸發由兩種方式,一種是通過this.$store.commit()函數,另外一種就是通過mapMutations來實現。
-
mutation只能用於修改數據,而Actions可以實現異步操作。
-
通過Actions的異步操作+mutation的修改數據,可以實現異步修改數據。調用Actions有兩種方式,第一種是通過this.$store.dispatch來調用,另外一種方式是通過mapActions來調用。
-
Getters函數用於對Store中數據進行加工,不會修改原本Store中的數據;Getters中的數據會受Store中數據進行影響。