title: Vuex入門
toc: true
date: 2018-09-23 22:30:52
categories:html
tags:vue
vuex文檔學習筆記。vuex
比較經常使用的兩種:shell
從https://unpkg.com/vuex下載後利用script標籤在vue後引入:npm
<script src="/path/to/vue.js"></script> <script src="/path/to/vuex.js"></script>
或api
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/vuex@3.0.1/dist/vuex.js"></script>
在項目目錄下運行:數組
npm install vuex --save
在模塊化的打包系統中利用這種方法時,必須顯式地利用Vue.use()
來安裝Vuex(而script標籤引入後是自動安裝的):promise
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
功能:把組件的共享狀態抽取出來,用一個全局單例模式管理。緩存
核心:store(倉庫),它包含了應用中的大部分state(狀態,驅動應用的數據源)。bash
這種全局單例模式管理和單純的全局變量的區別:
一個栗子:
// 若是在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } }) // 觸發狀態變動 store.commit('increment') console.log(store.state.count) // -> 1
再次強調,使用提交 mutation ,而不是直接改變 store.state.count
,
是由於咱們想要更明確地追蹤到狀態的變化。
固然,使用 Vuex 並不意味着須要將全部的狀態放入 Vuex。
雖然將全部的狀態放到 Vuex 會使狀態變化更顯式和易調試,但也會使代碼變得冗長和不直觀。
若是有些狀態嚴格屬於單個組件,最好仍是做爲組件的局部狀態。
你應該根據你的應用開發須要進行權衡和肯定。
每一個應用只包含一個 store 實例,它包含了全部須要vuex管理的狀態。
從 store 實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態:
// 建立一個 Counter 組件 const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count } } }
但這種模式致使組件依賴全局狀態單例。
store
選項爲了解決上述模式致使的組件依賴全局狀態單例的問題,
咱們能夠經過在根實例中註冊 store
選項——
這樣 store 實例會注入到根組件下的全部子組件中,
且子組件能經過 this.$store
訪問到:(需調用 Vue.use(Vuex)
)
// 根組件 const app = new Vue({ el: '#app', // 把 store 對象提供給 「store」 選項,這能夠把 store 的實例注入全部的子組件 store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` })
// 子組件 const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { // 經過 this.$store 訪問store return this.$store.state.count } } }
mapState
輔助函數當一個組件須要獲取多個狀態的時候,將這些狀態都聲明爲計算屬性會有些重複和冗餘。
爲了解決這個問題,咱們可使用 mapState
輔助函數幫助咱們生成計算屬性,讓你少按幾回鍵:
// 在單獨構建的版本中輔助函數爲 Vuex.mapState import { mapState } from 'vuex' export default { // ... computed: mapState({ // 箭頭函數可以使代碼更簡練 // 將 `this.count` 映射爲 `this.$store.state.count count: state => state.count, // 傳字符串參數 'count' 等同於 `state => state.count` countAlias: 'count', // 爲了可以使用 `this` 獲取局部狀態,必須使用常規函數 countPlusLocalState (state) { return state.count + this.localCount } }) }
當映射的計算屬性的名稱與 state 的子節點名稱相同時,咱們也能夠給 mapState
傳一個字符串數組。
computed: mapState([ // 映射 this.count 爲 store.state.count 'count' ])
ES6引入的新語法,由名字就能夠看出來這個操做符的含義:把對象展開,
來個栗子更容易理解:
var a = {'a':1, 'b':2, 'c':3}; {...a,'d':4} // {a: 1, b: 2, c: 3, d: 4} var b = [1,2,3]; [...b,4] // [1, 2, 3, 4]
有了這個操做符,咱們就能夠把mapState
函數和局部計算屬性混合使用了:
computed: { // 局部計算屬性 localComputed () { /* ... */ }, // 使用對象展開運算符將此對象展開混入到外部對象中 ...mapState({ // ... }) }
Vuex 容許咱們在 store 中定義「getter」(能夠認爲是 store 的計算屬性)。
就像計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。
Getter 會暴露爲 store.getters
對象,你能夠以屬性的形式訪問這些值;getter 在經過屬性訪問時是做爲 Vue 的響應式系統的一部分緩存其中的。
Getter 接受 state 做爲其第一個參數:
const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } }) store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也能夠接受其餘 getter 做爲第二個參數:
getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } } store.getters.doneTodosCount // -> 1
你也能夠經過讓 getter 返回一個函數,來實現給 getter 傳參。在你對 store 裏的數組進行查詢時很是有用。
getters: { // ... // getter 在經過方法訪問時,每次都會去進行調用,而不會緩存結果。 getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } } store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
咱們能夠很容易地在任何組件中使用getter:
computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }
咱們也可使用mapGetters
輔助函數將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex' export default { // ... computed: { // 使用對象展開運算符將 getter 混入 computed 對象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // 把 `this.doneCount` 映射爲 `this.$store.getters.doneTodosCount` doneCount: 'doneTodosCount', // ... ]) } }
更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。
Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。
這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { // 這裏的事件類型爲 'increment' increment (state) { // 變動狀態 state.count++ } } })
但咱們不能直接調用一個 mutation 回調函數,就像前面說的,咱們只能提交 mutation。
就像是事件註冊:「當觸發一個類型爲 increment
的 mutation 時,調用此函數。」
要喚醒一個 mutation handler,你須要以相應的 type 調用 store.commit 方法(即提交mutation):
store.commit('increment')
咱們還能夠向 store.commit
傳入額外的參數,即 mutation 的 載荷(payload):
// ... mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10)
在大多數狀況下,載荷應該是一個對象,這樣能夠包含多個字段,而且記錄的 mutation 會更易讀:
// ... mutations: { increment (state, payload) { state.count += payload.amount } } // ... store.commit('increment', { amount: 10 })
咱們可使用對象風格的提交方式,將一個直接包含 type
屬性的對象做爲載荷傳給 mutations :
store.commit({ type: 'increment', amount: 10 })
而且handler 無需改變:
mutations: { increment (state, payload) { state.count += payload.amount } }
由於 Vuex 的 store 中的狀態是響應式的,那麼當咱們使用Mutation變動狀態時,監視狀態的 Vue 組件也會自動更新。
所以使用 Vuex 中的 mutation 也須要像使用Vue 同樣遵照一些注意事項:
提早在 store 中初始化好全部所需屬性。
當須要在對象上添加新屬性時,你應該
使用 Vue.set(obj, 'newProp', 123)
, 或者
以新對象替換老對象。例如利用ES6的對象展開運算符:
state.obj = { ...state.obj, newProp: 123 }
mutation 必須是同步函數。爲何?請參考下面的例子:
mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } }
假設咱們正在 debug 一個 app 而且觀察 devtool 中的 mutation 日誌:
每一條 mutation 被記錄,devtools 都須要捕捉到前一狀態和後一狀態的快照。
然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:
當 mutation 觸發的時候,回調函數尚未被調用,devtools 不知道何時回調函數實際上被調用——
實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。
咱們能夠在組件中使用 this.$store.commit('xxx')
提交 mutation,
或者使用 mapMutations
輔助函數將組件中的 methods 映射爲 store.commit
調用(須要在根節點注入 store
):
import { mapMutations } from 'vuex' export default { // ... methods: { ...mapMutations([ 'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')` // `mapMutations` 也支持載荷: 'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')` }) } }
使用常量替代 mutation 事件類型在各類 Flux 實現中是很常見的模式。這樣可使 linter 之類的工具發揮做用,同時把這些常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然:
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 咱們可使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名 [SOME_MUTATION] (state) { // mutate state } } })
用不用常量取決於實際狀況——在須要多人協做的大型項目中,這會頗有幫助。你果真若是不想用,也徹底能夠不用。
Action 相似於 mutation,不一樣在於:
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { // 變動狀態 state.count++ } }, actions: { // 接受一個與 store 實例具備相同方法和屬性的 context 對象 increment (context) { // 提交mutation context.commit('increment') } } })
實踐中,咱們可使用 ES2015 的 參數解構 來簡化代碼(特別是咱們須要調用 commit
不少次的時候):
actions: { increment ({ commit }) { commit('increment') } }
由於action是提交mutation而不是直接變動狀態,所以咱們就能夠在action內部執行異步操做了:
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
Action 經過 store.dispatch
方法觸發:
store.dispatch('increment')
Actions 支持Mutation一樣的載荷方式和對象方式進行分發:
// 以載荷形式分發 store.dispatch('incrementAsync', { amount: 10 }) // 以對象形式分發 store.dispatch({ type: 'incrementAsync', amount: 10 })
也能夠在組件中使用 this.$store.dispatch('xxx')
分發 action,或者使用 mapActions
輔助函數將組件的 methods 映射爲 store.dispatch
調用(須要先在根節點注入 store
):
import { mapActions } from 'vuex' export default { // ... methods: { ...mapActions([ 'increment', // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')` // `mapActions` 也支持載荷: 'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)` ]), ...mapActions({ add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')` }) } }
Action 一般是異步的,那麼如何知道 action 何時結束呢?更重要的是,咱們如何才能組合多個 action,以處理更加複雜的異步流程?
store.dispatch
能夠處理被觸發的 action 的處理函數返回的 Promise,
而後返回這個 Promise:
actions: { actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } }
如今咱們就能夠:
store.dispatch('actionA').then(() => { // ... })
在另一個 action 中也能夠:
actions: { // ... actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) } }
若是咱們能夠利用 async / await,咱們還能夠以下組合 action:
// 假設 gotData() 和 gotOtherData() 返回的是 Promise actions: { async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) } }
一個
store.dispatch
在不一樣模塊中能夠觸發多個 action 函數。在這種狀況下,只有當全部觸發函數完成後,返回的 Promise 纔會執行。
因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。
爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的狀態 store.state.b // -> moduleB 的狀態
對於模塊內部的 mutation 和 getter,接收的第一個參數是模塊的局部狀態對象。
const moduleA = { state: { count: 0 }, mutations: { increment (state) { // 這裏的 `state` 對象是模塊的局部狀態 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } }
一樣,對於模塊內部的 action,局部狀態經過 context.state
暴露出來,根節點狀態則爲 context.rootState
:
const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
對於模塊內部的 getter,根節點狀態也會做爲第三個參數暴露出來:
const moduleA = { // ... getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } }
Vuex 並不限制咱們的代碼結構。可是,它規定了一些須要遵照的規則:
若是 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 # 產品模塊
有時候看不進去文檔,一邊總結一邊看就能看進去了 :)