由于多個狀態分散的跨越在許多組件和交互間各個角落,大型應用復雜度也經常逐漸增長。為了解決這個問題,Vue 提供 vuex:我們有受到 Elm 啟發的狀態管理庫。vuex 甚至集成到 vue-devtools,無需配置即可訪問時光旅行。
狀態管理
狀態的初始化
狀態管理,我們應該并不陌生。
舉個例子,超市里新進了一批商品,管理員給這些商品分類,建立索引,然后按照順序放入貨架的過程就是最簡單的狀態管理。
let goods1 = {
category: 'fruit',
name: 'apple',
quantity: 5
}
let goods2 = {
category: 'supplies',
name: 'toothbrush',
quantity: 5
}
let goods3 = {
category: 'clothes',
name: 'sweater',
quantity: 5
}
簡單歸類后 :
let shop = {
goods: {
fruit: [{ name: 'apple', quantity: 5 }],
supplies: [{ name: 'toothbrush', quantity: 5 }],
clothes: [{ name: 'sweater', quantity: 5 }]
}
}
這樣,當我們需要某一商品時,很容易根據類目檢索到這個商品 :
console.log(shop.goods.fruit.find(f => f.name === 'apple'))
//-> { name: 'apple', quantity: 5 }
狀態的改變
當有顧客前來購買商品時,我們需要類似的操作來減少被購買商品的數量 :
shop.goods.fruit.find(f => f.name === 'apple').quantity --
然而在成千上萬的交易量背后,你不知道這些商品被購買的詳細情況,你甚至不知道上周賣出了多少蘋果,你也就無從得知下周該進多少。
所以你需要一個賬目來記錄商品購買明細 :
let account = {
appleSold (value) {
console.log("apple sold " + value)
shop.goods.fruit.find(f => f.name === 'apple').quantity -= value
}
}
當賣出蘋果時,POS機“滴”一聲,記錄生成了 :
account.appleSold (5)
//-> apple sold 5
最簡單的store
于是,我們得到了一個最簡單的store :
let shop = {
goods: {
fruit: [{ name: 'apple', quantity: 5 }],
supplies: [{ name: 'toothbrush', quantity: 5 }],
clothes: [{ name: 'sweater', quantity: 5 }]
},
account: {
appleSold (value) {
console.log("apple sold " + value)
shop.goods.fruit.find(f => f.name === 'apple').quantity -= value
},
funcN () { }
}
}
由此可知,狀態管理可以幫助我們更友好的改變狀態,同時,跟蹤狀態變化的軌跡。
Vue(x) er 須知
開始
Vuex 官方文檔:
https://vuex.vuejs.org/zh-cn/getting-started.html
Vuex最核心的概念 :
- Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
- 你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
下面對此拓展:
對象引用
下面這兩段代碼將輸出什么? 先不要往下看, 自己寫一下
let store = {
state: {
msg: "welcome"
}
}, copy = store.state;
store.state = {
hello: "world"
};
console.log(Object.keys(copy));
let store = {
state: {
msg: 'welcome'
}
}, copy = store.state;
store.state.hello = "world";
console.log(Object.keys(copy))
結果如下(如果你都答對了,那么理解和上手Vuex將會很輕松) :
//-> ["msg"]
//-> ["msg", "hello"]
提交和分發
vuex 只是一個工具,或許過了這段時間,過了這個項目,你就不會再用它。
我們要記住的是它留給我們的啟示:
不要直接更改狀態, 而是通過提交(commit)和分發(dispatch)的方法通知管理者改變對象狀態,這是大型項目和復雜狀態管理的最佳實踐。
Vuex 核心概念
一個完整的 Vuex Store
/**
* index.js
*/
import axios from 'axios'
const store = new Vuex.Store({
state: {
counter: 0
},
getters: {
counter: state => state.counter
},
// 可處理異步請求, dispatch 觸發
actions: {
askPermission ({commit}) {
axios.get('/url').then((res) => {
if(res.data.permission)
commit('addCounter')
}).catch((err) => {
console.log('Error: in process "Ask permission".\n Detailed: ' + err)
})
}
},
// 同步, commit 觸發
mutations: {
addCounter (state) {
state.counter ++
}
}
})
PS: 仔細研究一下 dispatch & actions, commit & mutations, 是否有一種似曾相識的感覺?
Look, 看這對 emit & on (事件機制),同樣的事件類型,同樣的回調函數。
State
單一狀態樹
Vuex使用單一狀態樹,一個state對象包含全部應用層狀態,使得一個應用只有唯一數據源(SSOT, Single Source of Truth)
這對模塊化并不造成影響
state: {
moduleA: {
},
moduleB: {
}
}
Getter
state: {
prop: ''
}
你可以使用store.state.prop直接讀取狀態的值, 當然也可以使用Getter :
getters: {
prop = state => state.prop
}
使用Getter的好處在于,你可以從state中派生出一些狀態 :
getters: {
prop = state => state.prop,
fixedProp = state => state.prop || '暫無'
}
Mutation
Vuex 中的 mutation 類似于事件,有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler),回調函數的接受state作為第一個參數,我們在這里修改狀態(state)
state: {
counter: 0
},
mutations: {
addCounter (state) {
state.counter ++
},
addCounter (state, payload) {
state.counter += payload.value
}
}
通過 commit 通知狀態變化
store.commit('addCounter')
store.commit('addCounter', {value: 1})
Action
類似于mutation,不同在于
- 只能通過 commit mutation 通知狀態變化
- mutation 只能包含同步操作,而 action 可以包含異步操作(比如, 在這里可以執行ajax請求)
actions: {
askPermission ({commit}) {
axios.get('/url').then((res) => {
if(res.data.permission)
commit('addCounter')
}).catch((err) => {
console.log('Error: in process "Ask permission".\n Detailed: ' + err)
})
},
askPermission ({commit}, payload) {
axios.get('/url', { params:payload }).then((res) => {
if(res.data.permission)
commit('addCounter')
}).catch((err) => {
console.log('Error: in process "Ask permission".\n Detailed: ' + err)
})
}
}
通過 dispatch 通知狀態變化
store.dispatch('askPermission')
store.dispatch('askPermission', { author: "lonelydawn" })
Module
Vuex 允許我們將store分割成模塊,每個模塊擁有自己的state, mutation, action, getter, 甚至是嵌套子模塊 :
const store = new Vuex.Store({
modules: {
a: {
state: {},
mutations: {
addCounter(state) {}
},
actions: {},
getters: {}
},
b: {
namespaced: true, // 建立命名空間
state: {},
mutations: {
addCounter(state) {}
},
actions: {}
}
}
})
store.state.a
store.state.b
// 提交 給 模塊 a 的 mutations
store.commit('addCounter')
// 提交 給 模塊 b 的 mutations
store.commit('b/addCounter')
最后
Vuex 的基本用法已經介紹完了。
相關內容 :
官方文檔: https://vuex.vuejs.org/zh-cn/
官方實例: https://github.com/vuejs/vuex/tree/dev/examples
在下列內容中, 我將 演示如何使用 vue + vuex 以及其他常用組件從入門到實戰。