Vuex是一個專為Vue.js設計的狀態管理庫,適用于多組件共享狀態的場景。Vuex能集中式的存儲和維護所有組件的狀態,并提供相關規則保證狀態的獨立性、正確性和可預測性,這不僅讓調試變得可追蹤,還讓代碼變得更結構化且易維護。本文所使用的Vuex,其版本是3.1.1。
## 一、基本用法
  首先需要引入Vue和Vuex兩個庫,如果像下面這樣在Vue之后引入Vuex,那么Vuex會自動調用Vue.use()方法注冊其自身;但如果以模塊的方式引用,那么就得顯式地調用Vue.use()。注意,因為Vuex依賴Promise,所以對于那些不支持Promise的瀏覽器,要使用Vuex的話,得引入相關的polyfill庫,例如es6-promise。
~~~
<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>
~~~
  然后創建Vuex應用的核心:Store(倉庫)。它是一個容器,保存著大量的響應式狀態(State),并且這些狀態不能直接修改,需要顯式地將修改請求提交到Mutation(變更)中才能實現更新,因為這樣便于追蹤每個狀態的變化。在下面的示例中,初始化了一個digit狀態,并在mutations選項中添加了兩個可將其修改的方法。
~~~
const store = new Vuex.Store({
state: {
digit: 0
},
mutations: {
add: state => state.digit++,
minus: state => state.digit--
}
});
~~~
  接著創建根實例,并將store實例注入,從而讓整個應用都能讀寫其中的狀態,在組件中可通過$store屬性訪問到它,如下所示,以計算屬性的方式讀取digit狀態,并通過調用commit()方法來修改該狀態。
~~~
var vm = new Vue({
el: "#container",
store: store,
computed: {
digit() {
return this.$store.state.digit;
}
},
methods: {
add() {
this.$store.commit("add");
},
minus() {
this.$store.commit("minus");
}
}
});
~~~
  最后將根實例中的方法分別注冊到兩個按鈕的點擊事件中,如下所示,每當點擊這兩個按鈕時,狀態就會更新,并在頁面中顯示。
~~~
<div id="container">
<p>{{digit}}</p>
<button @click="add">增加</button>
<button @click="minus">減少</button>
</div>
~~~
## 二、主要組成
  Vuex的主要組成除了上一節提到的Store、State和Mutation之外,還包括Getter和Action,本節會對其中的四個做重點講解,它們之間的關系如圖2所示。
:-: 
圖2 四者的關系
**1)State**
  State是一個可存儲狀態的對象,在應用的任何位置都能被訪問到,并且作為單一數據源(Single Source Of Truth)而存在。
  當組件需要讀取大量狀態時,一個個的聲明成計算屬性會顯得過于繁瑣而冗余,于是Vuex提供了一個名為mapState()的輔助函數,用來將狀態自動映射成計算屬性,它的參數既可以是數組,也可以是對象。
  當計算屬性的名稱與狀態名稱相同,并且不需要做額外處理時,可將名稱組成一個字符串數組傳遞給mapState()函數,在組件中可按原名調用,如下所示。
~~~
var vm = new Vue({
computed: Vuex.mapState([ "digit" ])
});
~~~
  當計算屬性的名稱與狀態名稱不同,或者計算屬性讀取的是需要處理的狀態時,可將一個對象傳遞給mapState()函數,其鍵就是計算屬性的名稱,而其值既可以是函數,也可以是字符串,如下代碼所示。如果是函數,那么它的第一個參數是state,即狀態對象;如果是字符串,那么就是從state中指定一個狀態作為計算屬性。
~~~
var vm = new Vue({
computed: Vuex.mapState({
digit: state => state.digit,
alias: "digit" //相當于state => state.digit
})
});
~~~
  因為mapState()函數返回的是一個對象,所以當組件內已經包含計算屬性時,可以對其應用擴展運算符(...)來進行合并,如下所示,這是一種極為簡潔的寫法。
~~~
var vm = new Vue({
computed: {
name() {},
...Vuex.mapState([ "digit" ])
}
});
~~~
**2)Getter**
  Getter是從State中派生出的狀態,當多個組件要對同一個狀態進行相同的處理時,就需要將狀態轉移到Getter中,以免產生重復的冗余代碼。
  Getter相當于Store的計算屬性,它能接收兩個參數,第一個是state對象,第二個是可選的getters對象,該參數能讓不同的Getter之間相互訪問。Getter的返回值會被緩存,并且只有當依賴值發生變化時才會被重新計算。不過當返回值是函數時,其結果就不會被緩存,如下所示,其中caculate返回的是個數字,而sum返回的是個函數。
~~~
const store = new Vuex.Store({
state: {
digit: 0
},
getters: {
caculate: state => {
return state.digit + 2;
},
sum: state => right => {
return state.digit + right;
}
}
});
~~~
  在組件內可通過this.$store.getters訪問到Getter中的數據,如下所示,讀取了上一個示例中的兩個Getter。
~~~
var vm = new Vue({
methods: {
add() {
this.$store.getters.caculate;
this.$store.getters.sum(1);
}
}
});
~~~
  Getter也有一個輔助函數,用來將Getter自動映射為組件的計算屬性,名字叫mapGetters(),其參數也是數組或對象。但與之前的mapState()不同,當參數是對象時,其值不能是函數,只能是字符串,如下所示,為了對比兩種寫法,聲明了兩個computed選項。
~~~
var vm = new Vue({
computed: Vuex.mapGetters([ "caculate" ]),
computed: Vuex.mapGetters({
alias: "caculate"
})
});
~~~
**3)Mutation**
  更改狀態的唯一途徑是提交Mutation,Vuex中的Mutation類似于事件,也包含一個類型和回調函數,在函數體中可進行狀態更改的邏輯,并且它能接收兩個參數,第一個是state對象,第二個是可選的附加數據,叫載荷(Payload)。下面這個Mutation的類型是“interval”,接收了兩個參數。
~~~
const store = new Vuex.Store({
state: {
digit: 0
},
mutations: {
interval: (state, payload) => state.digit += payload.number
}
});
~~~
  在組件中不能直接調用Mutation的回調函數,得通過this.$store.commit()方法觸發更新,如下所示,采用了兩種提交方式,第一種是傳遞type參數,第二種是傳遞包含type屬性的對象。
~~~
var vm = new Vue({
methods: {
interval() {
this.$store.commit("interval", { number: 2 }); //第一種
this.$store.commit({ type: "interval", number: 2 }); //第二種
}
}
});
~~~
  當多人協作時,Mutation的類型適合寫成常量,這樣更容易維護,也能減少沖突。
~~~
const INTERVAL = "interval";
~~~
  Mutation有一個名為mapMutations()的輔助函數,其寫法和mapState()相同,它能將Mutation自動映射為組件的方法,如下所示。
~~~
var vm = new Vue({
methods: Vuex.mapMutations(["interval"])
//相當于
methods: {
interval(payload) {
this.$store.commit(INTERVAL, payload);
}
}
});
~~~
  注意,為了能追蹤狀態的變更,Mutation只支持同步的更新,如果要異步,那么得使用Action。
**4)Action**
  Action類似于Mutation,但不同的是它可以包含異步操作,并且只能用來通知Mutation,不會直接更新狀態。Action的回調函數能接收兩個參數,第一個是與Store實例具有相同屬性和方法的context對象(注意,不是Store實例本身),第二個是可選的附加數據,如下所示,調用commit()方法提交了一個Mutation。
~~~
const store = new Vuex.Store({
actions: {
interval(context, payload) {
context.commit("interval", payload);
}
}
});
~~~
  在組件中能通過this.$store.dispatch()方法分發Action,如下所示,與commit()方法一樣,它也有兩種提交方式。
~~~
var vm = new Vue({
methods: {
interval() {
this.$store.dispatch("interval", { number: 2 }); //第一種
this.$store.dispatch({type: "interval", number: 2}); //第二種
}
}
});
~~~
  注意,由于dispatch()方法返回的是一個Promise對象,因此它能以一種更優雅的方式來處理異步操作,如下所示。
~~~
var vm = new Vue({
methods: {
interval() {
this.$store.dispatch("interval", { number: 2 }).then(() => {
console.log("success");
});
}
}
});
~~~
  Action有一個名為mapActions()的輔助函數,其寫法和mapState()相同,它能將Action自動映射為組件的方法,如下所示。
~~~
var vm = new Vue({
methods: Vuex.mapActions([ "interval" ])
});
~~~
## 三、模塊
  當應用越來越大時,為了避免Store變得過于臃腫,有必要將其拆分到一個個的模塊(Module)中。每個模塊就是一個對象,包含屬于自己的State、Getter、Mutation和Action,甚至還能嵌套其它模塊。
**1)局部狀態**
  對于模塊內部的Getter和Mutation,它們接收的第一個參數是模塊的局部狀態,而Getter的第三個參數rootState和Action的context.rootState屬性可訪問根節點狀態(即全局狀態),如下所示。
~~~
const moduleA = {
state: { digit: 0 },
mutations: {
add: state => state.digit++
},
getters: {
caculate: (state, getter, rootState) => {
return state.digit + 2;
}
},
actions: {
interval(context, payload) {
context.commit("add", payload);
}
}
};
~~~
**2)命名空間**
  默認情況下,只有在訪問State時需要帶命名空間,而Getter、Mutation和Action的調用方式不變。將之前的moduleA模塊注冊到Store實例中,如下所示,modules選項的值是一個子模塊對象,其鍵是模塊名稱。
~~~
const store = new Vuex.Store({
modules: {
a: moduleA
}
});
~~~
  如果要訪問模塊中的digit狀態,那么可以像下面這樣寫。
~~~
store.state.a.digit;
~~~
  當模塊的namespaced屬性為true時,它的Getter、Mutation和Action也會帶命名空間,在使用時,需要添加命名空間前綴,如下代碼所示,此舉大大提升了模塊的封裝性和復用性。
~~~
const moduleA = {
namespaced: true
};
var vm = new Vue({
el: "#container",
store: store,
methods: {
add() {
this.$store.commit("a/add");
},
caculate() {
this.$store.getters["a/caculate"];
}
}
});
~~~
  如果要在帶命名空間的模塊中提交全局的Mutation或分發全局的Action,那么只要將{root: true}作為第三個參數傳給commit()或dispatch()就可實現,如下所示。
~~~
var vm = new Vue({
methods: {
add() {
this.$store.dispatch("add", null, { root: true });
this.$store.commit("add", null, { root: true });
}
}
});
~~~
  如果要在帶命名空間的模塊中注冊全局的Action,那么需要將其修改成對象的形式,然后添加root屬性并設為true,再將Action原先的定義轉移到handler()函數中,如下所示。
~~~
const moduleA = {
actions: {
interval: {
root: true,
handler(context, payload) {}
}
}
};
~~~
**3)輔助函數**
  當使用mapState()、mapGetters()、mapMutations()和mapActions()四個輔助函數對帶命名空間的模塊做映射時,需要顯式的包含命名空間,如下所示。
~~~
var vm = new Vue({
computed: Vuex.mapState({
digit: state => state.a.digit
}),
methods: Vuex.mapMutations({
add: "a/add"
})
});
~~~
  這四個輔助函數的第一個參數都是可選的,用于綁定命名空間,可簡化映射過程,如下所示。
~~~
var vm = new Vue({
computed: Vuex.mapState("a", {
digit: state => state.digit
}),
methods: Vuex.mapMutations("a", {
add: "add"
})
});
~~~
  Vuex還提供了另一個輔助函數createNamespacedHelpers(),可創建綁定命名空間的輔助函數,如下所示。
~~~
const { mapState, mapMutations } = Vuex.createNamespacedHelpers("a");
~~~
## 四、動態注冊
  在創建Store實例后,可通過registerModule()方法動態注冊模塊,如下代碼所示,調用了兩次registerModule()方法,第一次注冊了模塊“a”,第二次注冊了嵌套模塊“a/b”。
~~~
const store = new Vuex.Store();
store.registerModule("a", moduleA);
store.registerModule(["a", "b"], moduleAB);
~~~
  通過store.state.a和store.state.a.b可訪問模塊的局部狀態。如果要卸載動態注冊的模塊,那么可以通過unregisterModule()方法實現。
  registerModule()方法的第三個參數是可選的配置對象,當preserveState屬性的值為true時(如下所示),在注冊模塊時會忽略模塊中的狀態,即無法在store中讀取模塊中的狀態。
~~~
store.registerModule("a", moduleA, { preserveState: true });
~~~
## 五、表單處理
  表單默認能直接修改組件的狀態,但是在Vuex中,狀態只能由Mutation觸發更新。為了能更好的追蹤狀態的變化,也為了能更符合Vuex的思維,需要讓表單控件與狀態綁定在一起,并通過input或change事件監聽狀態更新的行為,如下所示。
~~~
<div id="container">
<input :value="digit" @input="add" />
</div>
~~~
  然后在Store實例中初始化digit狀態,并添加更新狀態的Mutation,如下所示。
~~~
const store = new Vuex.Store({
state: {
digit: 0
},
mutations: {
add: (state, value) => {
state.digit = value;
}
}
});
~~~
  最后在創建根實例時,將digit狀態映射成它的計算屬性,在事件處理程序add()中調用commit()方法,并將控件的值作為第二個參數傳入,如下所示。
~~~
var vm = new Vue({
el: "#container",
store: store,
computed: Vuex.mapState(["digit"]),
methods: {
add(e) {
this.$store.commit("add", e.target.value);
}
}
});
~~~
  還有一個方法也能實現相同的功能,那就是在控件上使用v-model指令,但需要與帶setter的計算屬性配合,如下所示(只給出了關鍵部分的代碼)。
~~~
<div id="container">
<input v-model="digit" />
</div>
<script>
var vm = new Vue({
computed: {
digit: {
get() {
return this.$store.state.digit;
},
set(value) {
this.$store.commit("add", value);
}
}
}
});
</script>
~~~
*****
> 原文出處:
[博客園-Vue躬行記](https://www.cnblogs.com/strick/category/1512864.html)
[知乎專欄-Vue躬行記](https://zhuanlan.zhihu.com/pwvue)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020