Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。html
Vuex 也集成到 Vue 的官方調試工具devtools extension,提供了諸如零配置的 time-tavel 調試、狀態快照導入導出等高級調試功能。vue
官方中文文檔webpack
一個簡單的 Vue 計數應用範例以下所示:git
new Vue({ // state data () { return { count: 0 } }, // view template: ` <div>{{ count }}</div> `, // actions methods: { increment () { this.count++ } } })
狀態自管理應用包含以下部分:es6
用下圖表示「單向數據流」理念的簡單示意:github
但在應用遇到 多組件共享狀態 時,單向數據流的簡潔性很容易遭到破壞:web
傳參的方法對於多層嵌套的組件將會很是繁瑣,而且對於兄弟組件間的狀態傳遞無能爲力。vue-router
常常會採用父子組件直接引用或者經過事件來變動和同步狀態的多份拷貝。以上的這些模式很是脆弱,一般會致使沒法維護的代碼。vuex
把組件的共享狀態抽取出來,以一個全局單例模式管理!!npm
在這種模式下,咱們的組件樹構成了一個巨大的「視圖」,無論在樹的哪一個位置,任何組件都能獲取狀態或者觸發行爲!
經過定義和隔離狀態管理中的各類概念並經過強制規則維持視圖和狀態間的獨立性,咱們的代碼將會變得更結構化且易維護。
以上也是Vuex背後的基本思想,借鑑了 Flux、Redux 和 The Elm Architecture。與其餘模式不一樣的是,Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
首先經過vue組件觸發,好比執行了一個按鈕事件,修改一個頁面中的數據。而後經過dispatch方法分發到Vuex(相似一個商店,包含有Actions、Mutations、State),將數據共享給Vuex中的State(必定是關聯於某個組件的某個標籤上);當要更改數據時,先分發到Actions,再提交給Mutations,隨後修改State。最後經過Render方法將數據渲染到vue組件中。
須要特別注意:Dispatch這一步是異步的,而Commit這一步是同步的。
Vuex不限制代碼結構,但規定了一些須要遵照的規則:
1)應用層級的狀態應集中到單個 store 對象中;
2)提交 mutation 是更改狀態的惟一方法,且這個過程是同步的;
3)異步邏輯都應該封裝在 action 裏面。
只要遵循上述規則,能夠任意組織代碼。若是遇到store文件過大的狀況,只需將 action、mutation 和 getter 分割到單獨的文件。
對於大型應用,會但願將 Vuex 相關代碼分割到模塊中。以下例所示:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API請求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 咱們組裝模塊並導出 store 的地方
├── actions.js # 根級別的 action
├── mutations.js # 根級別的 mutation
└── modules
├── cart.js # 購物車模塊
└── products.js # 產品模塊
CDN地址:https://unpkg.com/vuex,該連接一直指向NPM上發佈的最新版本。
還可使用 https://unpkg.com/vuex@2.0.0 這樣的方式指定特定的版本。
當使用全局script標籤引用 vuex 時,會進行自動安裝:
<script src="/path/to/vue.js"></script> <script src="/path/to/vuex.js"></script>
npm install vuex --save
在模塊化的打包系統中,必須顯式地經過 Vue.use()來安裝Vuex:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
建立測試項目以下所示:
$ vue init webpack vuex-demo ? Project name vuex-demo ? Project description A Vue.js project ? Author xiugeng <443614404@qq.com> ? Vue build standalone ? Install vue-router? No ? Use ESLint to lint your code? No ? Set up unit tests No ? Setup e2e tests with Nightwatch? No ? Should we run `npm install` for you after the project has been created? (recom mended) npm vue-cli · Generated "vuex-demo".
安裝vuex組件:
$ cd vuex-demo/ $ npm install vuex -S
「Store」基本上就是一個容器,它包含着應用中大部分的狀態(state)。
1)Vuex的狀態存儲是響應式的
當Vue組件從 Store 中讀取狀態的時候,若 Store 中狀態發送變化,那麼相應的組件也會相應獲得高效更新。
2)不能直接改變 Store 中的狀態
改變 Store 中的狀態的惟一途徑就是下顯式地提交(commit)mutation。這樣使得能夠方便地跟蹤每一個狀態的變化,從而可以經過實現一些工具幫助更好地瞭解本身的應用。
安裝 Vuex 後,建立一個 store。建立 src/store 目錄,再建立 src/store/index.js 文件,用做組裝模塊並導出store。
import Vue from 'vue' import Vuex from 'vuex' // 使用插件(若是在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex)) Vue.use(Vuex); const store = new Vuex.Store({ // 五大將:state mutation action getter module state:{ count: 1 }, mutations:{ }, actions:{ } }); export default store;
在 src/main.js 文件中引入Store,將store保存在組件中,共享store狀態:
import Vue from 'vue' import App from './App' import store from './store/index' Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: '#app', store, // store保存在組件中,可共享store狀態 components: { App }, template: '<App/>' })
在修改 src/components/HelloWorld.vue,使用computed實時監聽狀態對象:
<template> <div class="hello"> <h2>{{myCount}}</h2> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, computed: { myCount(){ // 經過 store.state 來獲取狀態對象 return this.$store.state.count; } } } </script>
因爲 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅須要在計算屬性中返回便可。
顯示效果:
建立組件 src/components/Child.vue,編碼內容以下所示:
<template> <div> <p>{{myCount}}</p> </div> </template> <script> export default { name: "Child", data() { return { }; }, computed: { myCount(){ return this.$store.state.count; } } }; </script>
在組件 src/components/HelloWorld.vue 中引入子組件,造成父子關係:
<template> <div class="hello"> <h2>{{myCount}}</h2> <!-- 渲染子組件 --> <Child/> </div> </template> <script> // 引入子組件,造成父子關係 import Child from './Child' export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, computed: { myCount(){ // 經過 store.state 來獲取狀態對象 return this.$store.state.count; } }, components: { Child } } </script>
顯示效果以下所示:
更改 Vuex 的 store 中的狀態(state)的惟一方法是提交(commit) mutation。Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。
修改 HelloWorld.vue 文件:
<template> <div class="hello"> <h2>{{myCount}}</h2> <!-- 渲染子組件 --> <Child/> <button @click="change">修改</button> </div> </template> <script> // 引入子組件,造成父子關係 import Child from './Child' export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, computed: { myCount(){ // 經過 store.state 來獲取狀態對象 return this.$store.state.count; } }, components: { Child }, methods: { change(){ // 修改狀態 this.$store.commit('addCount',3) } } } </script>
再在 store/index.js中聲明 mutations 方法 addCount:
const store = new Vuex.Store({ // 五大將:state mutation action getter module state:{ count: 1 }, mutations:{ // 聲明方法 // 只能作同步操做,不能直接commit addCount(state, val) { state.count += val; } }, actions:{ } });
顯示效果:
點擊修改按鈕,頁面上顯示的數字從1》4》7》10》13實時修改變換。
經過提交 mutation 的方式,而非直接改變 store.state.count
,是由於咱們想要更明確地追蹤到狀態的變化。這個簡單的約定可以讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓咱們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,咱們甚至能夠實現如時間穿梭般的調試體驗。
Mutation 重要的原則就是要記住 mutation 必須是同步函數。
每一條 mutation 被記錄,devtools 都須要捕捉到前一狀態和後一狀態的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:由於當 mutation 觸發的時候,回調函數尚未被調用,devtools 不知道何時回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。
<template> <div class="hello"> <h2>{{myCount}}</h2> <!-- 渲染子組件 --> <Child/> <button @click="change">同步修改</button> <button @click="asyncHandler">異步修改</button> </div> </template> <script> // 引入子組件,造成父子關係 import Child from './Child' export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, computed: { myCount(){ // 經過 store.state 來獲取狀態對象 return this.$store.state.count; } }, components: { Child }, methods: { change(){ // 修改狀態,更改 Vuex的store中的狀態state的惟一方法是提交(commit)mutation this.$store.commit('addCount',3); }, asyncHandler(){ this.$store.commit('asyncHandler',1); } } } </script>
在src/store/index.js中添加異步操做:
import Vue from 'vue' import Vuex from 'vuex' // 使用插件(若是在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex)) Vue.use(Vuex); const store = new Vuex.Store({ // 五大將:state mutation action getter module state:{ count: 1 }, mutations:{ // 聲明方法 // 只能作同步操做,不能直接commit addCount(state, val) { state.count += val; // 同步操做 }, asyncHandler(state, val) { setTimeout(()=>{ // 異步操做 state.count += val; }, 2000); }, }, actions:{ } }); export default store;
1.使用Vue Devtool調試系統:
2.點擊同步修改:
3.點擊異步修改:
注意:異步操做執行後,頁面上的值發生了修改,但卻沒有修改state中的值。其餘組件調用state值時會出現數據對不上。
Action 相似於 mutation,不一樣在於:
註冊一個簡單action示例:
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } } })
Action 函數接受一個與 store 實例具備相同方法和屬性的 context 對象,所以你能夠調用 context.commit
提交一個 mutation,或者經過 context.state
和 context.getters
來獲取 state 和 getters。
實踐中,一般使用 ES2015 的參數解構 來簡化代碼(尤爲是須要屢次調用 commit 的場景)。
src/store/index.js 修改以下所示:
import Vue from 'vue' import Vuex from 'vuex' // 使用插件(若是在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex)) Vue.use(Vuex); const store = new Vuex.Store({ // 五大將:state mutation action getter module state:{ count: 1 }, mutations:{ // 聲明方法 // 只能作同步操做,不能直接commit addCount(state, val) { state.count += val; // 同步操做 }, asyncHandler(state, val) { // 只作同步操做 state.count += val; }, }, actions:{ // Action相似mutation,但Action提交的是mutation,不直接變動狀態,且能夠包含任意異步操做 addCount({commit},val) { commit('addCount',val); }, asyncHandler({commit},val) { setTimeout(()=>{ commit('asyncHandler', val); },2000) } } }); export default store;
Action 經過 store..dispatch 方法觸發,在組件中使用 this.$store.dispatch('xxx')
分發 action,修改HelloWorld.vue以下所示:
<template> <div class="hello"> <h2>{{myCount}}</h2> <!-- 渲染子組件 --> <Child/> <button @click="change">同步修改</button> <button @click="asyncHandler">異步修改</button> </div> </template> <script> // 引入子組件,造成父子關係 import Child from './Child' export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, computed: { myCount(){ // 經過 store.state 來獲取狀態對象 return this.$store.state.count; } }, components: { Child }, methods: { change(){ // 修改狀態,更改 Vuex的store中的狀態state的惟一方法是提交(commit)mutation this.$store.dispatch('addCount',3); }, asyncHandler(){ this.$store.dispatch('asyncHandler',1); } } } </script>
異步修改state的值,問題解決,點擊兩次同步修改和一次異步修改後,顯示以下所示:
之因此不直接分發 mutation 而要多這一步操做,主要是由於 mutation必須同步執行這個限制。而Action則沒有這個約束,能夠在action內部執行異步操做。