官方解釋javascript
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用 集中式存儲 管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension
,提供了諸如零配置的 time-travel
調試、狀態快照導入導出等高級調試功能。html
狀態管理究竟是什麼?vue
狀態管理模式、集中式存儲管理這些名詞聽起來就很是高大上,讓人捉摸不透。其實你能夠簡單的將其當作把須要多個組件共享的變量所有存儲在一個對象裏面。而後將這個對象放在頂層的Vue實例中讓其餘組件可使用。那麼多個組件是否是就能夠共享這個對象中的全部變量屬性了呢?是的java
若是是這樣的話爲何官方還要專門出一個插件Vuex呢?難道咱們不能本身封裝一個對象來管理嗎?固然能夠,只是咱們要先想一想VueJS帶給咱們最大的便利是什麼呢?沒錯,就是響應式。若是你本身封裝實現一個對象能不能保證它裏面全部的屬性作到響應式呢?固然也能夠,只是本身封裝可能稍微麻煩一些。不用懷疑,Vuex就是爲了提供這樣一個在多個組件間共享狀態的插件,用它就能夠了。vuex
管理什麼狀態?npm
可是有什麼狀態是須要咱們在多個組件間共享的呢?若是你作過大型開放必定遇到過多個狀態在多個界面間的共享問題。好比用戶的登陸狀態、用戶名稱、頭像、地理位置信息等。好比商品的收藏、購物車中的物品等。這些狀態信息均可以放在統一的地方對它進行保存和管理,並且它們仍是響應式的。promise
OK,從理論上理解了狀態管理以後,讓咱們從實際的代碼再來看看狀態管理。網絡
理解app
咱們知道,要在單個組件中進行狀態管理是一件很是簡單的事情。異步
什麼意思呢?咱們來看下面的圖片。這圖片中的三種東西,怎麼理解呢?
State:不用多說,就是咱們的狀態。(你姑且能夠當作就是data中的屬性)
View:視圖層,能夠針對State的變化,顯示不一樣的信息。(這個好理解)
Actions:這裏的Actions主要是用戶的各類操做:點擊、輸入等會致使狀態的改變。
實現
在下面案例中,咱們有木有狀態須要管理呢?沒錯就是counter。counter須要某種方式被記錄下來,也就是咱們的State。counter目前的值須要被顯示在界面中,也就是咱們的View部分。
界面發生某些操做時(咱們這裏是用戶的點擊,也能夠是用戶的input),須要去更新狀態,也就是咱們的Actions,這不就是上面的流程圖了嗎?
<template> <div id="app"> <div>當前計數:{{ counter }}</div> <button @click="counter+=1">+1</button> <button @click="counter-=1">-1</button> </div> </template> <script> export default { name: "app", data() { return { counter: 0 } } } </script> <style scoped></style>
Vue已經幫咱們作好了單個界面的狀態管理,可是若是是多個界面呢?
也就是說對於某些狀態(狀態1/狀態2/狀態3)來講只屬於咱們某一個試圖,可是也有一些狀態(狀態a/狀態b/狀態c)屬於多個試圖共同想要維護的。
全局單例模式(大管家)
npm install vuex --save
使用Vuex實現一下以前的計數器案例
store/index.js
import VueX from 'vuex' import Vue from 'vue' Vue.use(VueX) const store = new VueX.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } } }) export default store
將store掛載到Vue實例中
咱們讓全部的Vue組件均可以使用這個store對象,來到main.js文件導入store對象,而且放在new Vue中。這樣在其餘Vue組件中,咱們就能夠經過this.$store的方式獲取到這個store對象了。
//... import store from '@/store' //... new Vue({ store, render: h => h(App), }).$mount('#app')
組件中使用Vuex的count
<template> <div id="app"> <div>當前計數:{{ count }}</div> <button @click="increment">+1</button> <button @click="decrement">-1</button> </div> </template> <script> export default { name: "app", computed: { count() { return this.$store.state.count } }, methods: { increment() { this.$store.commit('increment') }, decrement() { this.$store.commit('decrement') } } } </script> <style scoped></style>
好的,上面就是使用Vuex最簡單的方式了。
咱們來對使用步驟,作一個簡單的總結:
new Vue
對象中,這樣能夠保證在全部的組件中均可以使用到。this.$store.state
屬性的方式來訪問狀態this.$store.commit('mutation中的方法')
來修改狀態注意:
① 咱們經過提交mutation的方式,而非直接改變store.state.count
② 這是由於Vuex能夠更明確的追蹤狀態的變化,因此不要直接改變store.state.count的值。
咱們來對這幾個概念一一理解
State單一狀態樹
Vuex提出使用單一狀態樹, 什麼是單一狀態樹呢?英文名稱是Single Source of Truth,也能夠翻譯成單一數據源。
可是它是什麼呢?咱們來看一個生活中的例子。咱們知道在國內咱們有不少的信息須要被記錄,好比上學時的我的檔案,工做後的社保記錄,公積金記錄,結婚後的婚姻信息,以及其餘相關的戶口、醫療、文憑、房產記錄等等(還有不少信息)。這些信息被分散在不少地方進行管理,有一天你須要辦某個業務時(好比入戶某個城市),你會發現你須要到各個對應的工做地點去打印、蓋章各類資料信息,最後到一個地方提交證實你的信息無誤。這種保存信息的方案不只僅低效並且不方便管理,以及往後的維護也是一個龐大的工做(須要大量的各個部門的人力來維護,固然國家目前已經在完善咱們的這個系統了)。
這個和咱們在應用開發中比較相似:若是你的狀態信息是保存到多個Store對象中的,那麼以後的管理和維護等等都會變得特別困難。因此Vuex也使用了單一狀態樹來管理應用層級的所有狀態。單一狀態樹可以讓咱們最直接的方式找到某個狀態的片斷,並且在以後的維護和調試過程當中也能夠很是方便的管理和維護。
相似於組件中的計算屬性computed
有時候咱們須要從store中獲取一些state變化後的狀態,好比下面的Store中獲取學生年齡大於20的學生個數。
state: { students: [ {id: 110, name: 'polaris',age: 18}, {id: 111, name: 'rose',age: 22}, {id: 112, name: 'jack',age: 34}, {id: 113, name: 'tom',age: 11}, ] },
咱們能夠在Store中定義getters
getters: { greateAgesCount: state => { return state.students.filter(s => s.age >= 20).length; } } //若是咱們已經有了一個獲取全部年齡大於20歲學生列表的getters, 那麼代碼也能夠這樣來寫 getters: { greateAgesStudents: state => { return state.students.filter(s => s.age >= 20); }, greateAgesCount: (state, getters) => { return getters.greateAgesStudents.length; } }
//組件中獲取getters計算後的值 computed: { greateAgesCount() { return this.$store.getters.greateAgesCount } },
getters默認是不能傳遞參數的,若是但願傳遞參數,那麼只能讓getters自己返回另外一個函數。好比上面的案例中咱們但願根據ID獲取用戶的信息。
getters: { studentById: state => { return id => { return state.students.find(s => s.id === id) } } }
computed: { studentById() { return this.$store.getters.studentById(112) } }
Vuex的store狀態的更新惟一方式:提交Mutation
Mutation主要包括兩部分:
字符串的 事件類型type
一個 回調函數 handler
,該回調函數的第一個參數就是state。
mutation的定義方式:
//以下:increment就是事件類型,(state) {state.count++;}是回調函數 mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } }
在某個組件中經過mutation更新state值
increment: function() { this.$store.commit('increment'); }
在經過mutation更新數據的時候,有可能咱們但願攜帶一些 額外的參數,參數被稱爲是mutation的載荷(Payload)
Mutation中的代碼:
mutations: { increment(state,n) { state.count += n; }, decrement(state,n) { state.count -= n; } }
在某個組件中經過mutation更新state值
methods: { increment() { this.$store.commit('increment',2) }, decrement() { this.$store.commit('decrement',2) } }
可是若是參數不是一個呢?好比咱們有不少參數須要傳遞,這個時候咱們一般會以對象的形式傳遞,也就是payload是一個對象。
changeCount(state,payload) { state.count = payload.count }
changeCount() { this.$store.commit('changeCount',{count: 5}) }
上面的經過commit進行提交是一種普通的方式
Vue還提供了另一種風格, 它是一個包含type屬性的對象
changeCount() { this.$store.commit({ type: 'changeCount', count: 100 }) }
Mutation中的處理方式是將整個commit的對象做爲payload使用, 因此代碼沒有改變依然以下:
changeCount(state,payload) { state.count = payload.count }
Vuex的store中的state是響應式的,當state中的數據發生改變時Vue組件會自動更新。
這就要求咱們必須遵照一些Vuex對應的規則
提早在store中初始化好所需的屬性
當給state中的對象添加新屬性時,,使用下面的方式
Vue.set(obj, 'newProp', 123)
方式二:用新對象給舊對象從新賦值
state中的對象添加新屬性的案例
咱們來看一個例子:當咱們點擊更新信息時界面並無發生對應改變,如何才能讓它改變呢?
import VueX from 'vuex' import Vue from 'vue' Vue.use(VueX) const store = new VueX.Store({ state: { info: { name: 'polaris', age: 18 } }, mutations: { updateInfo(state,payload) { state.info['height'] = payload.height } } }) export default store
<template> <div id="app"> <p>個人我的信息:{{info}}</p> <button @click="updateInfo">更新信息</button> </div> </template> <script> export default { name: "app", computed: { info() { return this.$store.state.info } }, methods: { updateInfo() { this.$store.commit('updateInfo',{height: 1.88}) } } } </script> <style scoped></style>
下面代碼的方式一和方式二,均可以讓state中的屬性是響應式的
mutations: { // updateInfo(state,payload) { // state.info['height'] = payload.height // } updateInfo(state, payload) { //方式一 // Vue.set(state.info,'height',payload.height) //方式二 state.info = {...state.info, 'height': payload.height} } }
咱們也能夠響應式的刪除某個對象的屬性如:
Vue.delete(state.info,'height')
概念
咱們來考慮一個問題,在mutation中咱們定義了不少事件類型(也就是其中的方法名稱)。當咱們的項目增大時Vuex管理的狀態愈來愈多,須要更新狀態的狀況愈來愈多,那麼意味着Mutation中的方法愈來愈多。方法過多使用者須要花費大量的經歷去記住這些方法甚至是多個文件間來回切換查看方法名稱,甚至若是不是複製可能還會出現寫錯的狀況。
如何避免上述的問題呢?在各類Flux實現中,一種很常見的方案就是 使用常量替代Mutation事件的類型 。咱們能夠將這些常量放在一個單獨的文件中方便管理以及讓整個app全部的事件類型一目瞭然。
具體怎麼作呢?咱們能夠建立一個文件 mutation-types.js
, 而且在其中定義咱們的常量。
定義常量時咱們可使用ES2015中的風格,使用一個常量來做爲函數的名稱。
代碼
一般狀況下Vuex要求咱們Mutation中的方法必須是同步方法。
主要的緣由是當咱們使用devtools時,利用devtools幫助咱們捕捉mutation的快照,可是若是是異步操做那麼devtools將不能很好的追蹤這個操做何時會被完成。即若是Vuex中的代碼咱們使用了異步函數,你會發現state中的info數據一直沒有被改變由於它沒法追蹤到。因此一般狀況下不要在mutation中進行異步的操做。
mutations: { updateInfo(state) { setTimeout(() => { state.info.name = "GG"; },1000) } },
前面咱們強調不要再Mutation中進行異步操做,可是某些狀況咱們確實但願在Vuex中進行一些異步操做,好比網絡請求必然是異步的,這個時候怎麼處理呢?
Action相似於Mutation,可是是用來代替Mutation進行異步操做的。
Action的基本使用代碼以下
mutations: { updateInfo(state) { // setTimeout(() => { // state.info.name = "GG"; // },1000) state.info.name = "GG"; } }, actions: { actUpdateInfo(context) { setTimeout(() => { context.commit('updateInfo'); },1000) } }
context是什麼?context是和store對象具備相同方法和屬性的對象,也就是說咱們能夠經過context去進行commit相關的操做,也能夠獲取context.state等。可是注意這裏它們並非同一個對象,爲何呢? 咱們後面學習Modules的時候再具體說。
這樣的代碼是否畫蛇添足呢?咱們定義了actions,而後又在actions中去進行commit,這不是脫褲放屁嗎?事實上並非這樣,若是在Vuex中有異步操做那麼咱們就能夠在actions中完成了。
在Vue組件中, 若是咱們調用action中的方法,那麼就須要使用dispatch,一樣的dispatch也是支持傳遞payload
methods: { updateInfo() { // this.$store.commit('updateInfo'); this.$store.dispatch('actUpdateInfo'); } }
const obj = { name: 'why', age: 18, height: 1.88 }; //順序可變 const {age, name, height} = obj; console.log(name);
在Actions中使用對象的解構寫法
getters 和 mutations 固然也可使用對象的解構寫法
actions: { actUpdateInfo({commit}) { setTimeout(() => { commit('updateInfo'); },1000) } }
不清楚promise的用法請回看第八章
引入
當咱們的store中異步操做執行結束後,是否可以提醒一下調用者已經成功執行了呢?
咱們能夠這樣實現:
actions: { actUpdateInfo(context,success) { setTimeout(() => { context.commit('updateInfo'); success(); },1000) } },
methods: { updateInfo() { this.$store.dispatch('actUpdateInfo',() => { console.log('執行成功!'); }); } }
可是這樣就不能傳入其餘參數了,那咱們再換種寫法!
actions: { actUpdateInfo(context,payload) { setTimeout(() => { context.commit('updateInfo',payload.message); console.log(payload.message); payload.success(); },1000) } },
methods: { updateInfo() { this.$store.dispatch('actUpdateInfo', { message: '我是攜帶的參數', success: () => { console.log('執行成功!'); } }); } }
雖然能夠實現,可是回調的信息和攜帶的參數寫到一塊兒去了,這種作法是不夠優雅的,下面咱們經過Promise實現!
使用Promise
前面咱們學習ES6語法的時候說過Promise常常用於異步操做。在Action中咱們能夠將異步操做放在一個Promise中,而且在成功或者失敗後調用對應的resolve或reject。
actions: { actUpdateInfo(context, payload) { return new Promise((resolve, reject) => { setTimeout(() => { context.commit('updateInfo'); console.log(payload); resolve('執行成功!'); }, 1000) }) } },
methods: { updateInfo() { this.$store.dispatch("actUpdateInfo", '我是攜帶的信息').then(res => { console.log(res); }); }, },
Module是模塊的意思,爲何在Vuex中咱們要使用模塊呢?
Vue使用單一狀態樹,那麼也意味着不少狀態都會交給Vuex來管理。當應用變得很是複雜時store對象就有可能變得至關臃腫。爲了解決這個問題Vuex容許咱們將store分割成模塊(Module),而每一個模塊擁有本身的state,mutations,actions,getters等。
咱們按照什麼樣的方式來組織模塊呢?看下面代碼
//注意:模塊中mutation和getters接收的第一個參數state,context是局部狀態對象。 const moduleA = { state: { name: 'polaris' }, mutations: { updateName(state) { state.name = 'GG'; } }, actions: { actUpdateName(context) { setTimeout(() => { context.commit('updateName') },1000) } }, getters: { fullName(state) { return state.name + "hahaha"; } } } const moduleB = { state: { name: 'rose' }, mutations: {}, actions: {}, getters: {} } const store = new Vuex.store({ modules: { a: moduleA, b: moduleB } })
//state,調用時必須加上模塊名,不一樣模塊間能夠有相同的值 this.$store.state.a.name //獲取moduleA的狀態中的值 this.$store.state.b.name //獲取moduleB的狀態中的值 //mutations,不一樣模塊間能夠有相同的值可是不要這麼寫,由於外部會同時調用不一樣模塊的mutations方法 updateName() { this.$store.commit('updateName'); //依次去模塊中找 } //getters,不一樣模塊間不能有相同的值,會報錯 this.$store.getters.fullName //依次去模塊中找 //actions,不一樣模塊有相同的mutations方法時,會同時調用不一樣模塊的mutations方法 actUpdateName() { this.$store.dispatch('actUpdateName') } //=> 總結:除了state,各個模塊中的其餘內容不要重名!