狀態(tài)管理模式氮昧,依賴Promise
單項(xiàng)數(shù)據(jù)流
多組件共享狀態(tài)時(shí)框杜,單項(xiàng)數(shù)據(jù)流會(huì)被破壞 ===》抽取組件的共享狀態(tài)。以一個(gè)全局單例模式管理
在這種模式下袖肥,我們的組件樹構(gòu)成了一個(gè)巨大的“視圖”咪辱,不管在樹的哪個(gè)位置,任何組件都能獲取狀態(tài)或者觸發(fā)行為椎组!
1油狂、store 倉(cāng)庫(kù)
包含著你的應(yīng)用中大部分的狀態(tài) (state)。Vuex 和單純的全局對(duì)象有以下兩點(diǎn)不同:
1.1、Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的专筷。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時(shí)候弱贼,若 store 中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會(huì)相應(yīng)地得到高效更新磷蛹。
1.2吮旅、你不能直接改變 store 中的狀態(tài)。改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation味咳。這樣使得我們可以方便地跟蹤每一個(gè)狀態(tài)的變化鸟辅,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用。
2莺葫、state
vue使用單一狀態(tài)樹匪凉,每個(gè)應(yīng)用將僅僅包含一個(gè) store 實(shí)例
Vuex 通過(guò) store 選項(xiàng),提供了一種機(jī)制將狀態(tài)從根組件“注入”到每一個(gè)子組件中(需調(diào)用 Vue.use(Vuex))
const app = new Vue({
el: '#app',
// 把 store 對(duì)象提供給 “store” 選項(xiàng)捺檬,這可以把 store 的實(shí)例注入所有的子組件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
mapState 輔助函數(shù)
對(duì)象展開(kāi)運(yùn)算符
3再层、getter
可以理解成store的計(jì)算屬性
4、mutation
改變store中狀態(tài)的唯一途徑就是提交mutation
必須是同步函數(shù)
可以在mutation中提交多個(gè)參數(shù)堡纬,第二個(gè)參數(shù)叫載荷payload
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
//...
store.commit('increment', {
amount: 10
})
//對(duì)象風(fēng)格
store.commit({
type: 'increment',
amount: 10
})
可以 使用常量代替mutation提交類型
mutations: {
// 我們可以使用 ES2015 風(fēng)格的計(jì)算屬性命名功能來(lái)使用一個(gè)常量作為函數(shù)名
[SOME_MUTATION] (state) {
// mutate state
}
}
5聂受、action
類似于mutation,區(qū)別:提交的是mutation烤镐;異步
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
//接受一個(gè)與 store 實(shí)例具有相同方法和屬性的 context 對(duì)象蛋济,所以可以commit、getter等
increment (context) {
context.commit('increment')
}
}
})
分發(fā)store.dispatch炮叶?碗旅??镜悉?祟辟?
6、module
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 的狀態(tài)
store.state.b // -> moduleB 的狀態(tài)
如果希望你的模塊具有更高的封裝度和復(fù)用性侣肄,你可以通過(guò)添加 namespaced: true 的方式使其成為帶命名空間的模塊旧困。
7、項(xiàng)目結(jié)構(gòu)
8稼锅、插件
8.1吼具、就是一個(gè)函數(shù),接受store作為唯一的參數(shù)
const myPlugin = store => {
// 當(dāng) store 初始化后調(diào)用
store.subscribe((mutation, state) => {
// 每次 mutation 之后調(diào)用
// mutation 的格式為 { type, payload }
})
}
// ...
const store = new Vuex.Store({
// ...
plugins: [myPlugin]
})
注意插件內(nèi)也不能直接修改store矩距,只能通過(guò)mutation來(lái)修改拗盒,
8.2、有時(shí)候插件需要獲得狀態(tài)的“快照”剩晴,比較改變的前后狀態(tài)锣咒。想要實(shí)現(xiàn)這項(xiàng)功能,你需要對(duì)狀態(tài)對(duì)象進(jìn)行深拷貝
const myPluginWithSnapshot = store => {
let prevState = _.cloneDeep(store.state)
store.subscribe((mutation, state) => {
let nextState = _.cloneDeep(state)
// 比較 prevState 和 nextState...
// 保存狀態(tài)赞弥,用于下一次 mutation
prevState = nextState
})
}
生成狀態(tài)快照的插件應(yīng)該只在開(kāi)發(fā)環(huán)境中使用毅整,這一點(diǎn)個(gè)可以通過(guò)webpack來(lái)控制
const store = new Vuex.Store({
// ...
plugins: process.env.NODE_ENV !== 'production'
? [myPluginWithSnapshot]
: []
})
8.3、logger插件绽左,日志插件用來(lái)一般的調(diào)試悼嫉,會(huì)生成狀態(tài)快照,僅在開(kāi)發(fā)環(huán)境使用
import createLogger from 'vuex/dist/logger'
const store = new Vuex.Store({
plugins: [createLogger()]
})
//...
//幾個(gè)配置項(xiàng)
const logger = createLogger({
collapsed: false, // 自動(dòng)展開(kāi)記錄的 mutation
filter (mutation, stateBefore, stateAfter) {
// 若 mutation 需要被記錄拼窥,就讓它返回 true 即可
// 順便戏蔑,`mutation` 是個(gè) { type, payload } 對(duì)象
return mutation.type !== "aBlacklistedMutation"
},
transformer (state) {
// 在開(kāi)始記錄之前轉(zhuǎn)換狀態(tài)
// 例如,只返回指定的子樹
return state.subTree
},
mutationTransformer (mutation) {
// mutation 按照 { type, payload } 格式記錄
// 我們可以按任意方式格式化
return mutation.type
},
logger: console, // 自定義 console 實(shí)現(xiàn)鲁纠,默認(rèn)為 `console`
})
9总棵、嚴(yán)格模式
const store = new Vuex.Store({
// ...
// 不要在發(fā)布環(huán)境中開(kāi)啟嚴(yán)格模式航瞭,在嚴(yán)格模式下龄广,無(wú)論何時(shí)發(fā)生了狀態(tài)變更且不是由 mutation 函數(shù)引起的秘案,將會(huì)拋出錯(cuò)誤硕淑。有可能造成性能損失
strict: process.env.NODE_ENV !== 'production'
})
10休偶、表單處理
對(duì)于希望對(duì)vuex中store里數(shù)據(jù)進(jìn)行雙向綁定時(shí)
10.1座硕、vuex的思路
<input :value="message" @input="updateMessage">
//...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
//...
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
10.2莱没、使用帶有 setter 的雙向綁定計(jì)算屬性
<input v-model="message">
//...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
11胁编、測(cè)試
檢測(cè)state值是否達(dá)到預(yù)期鹃觉,那么我們主要是針對(duì) Vuex 中的 mutation 和 action 進(jìn)行單元測(cè)試专酗,
11.1、motation
比較好測(cè)盗扇,就是一群一欄參數(shù)的函數(shù) 祷肯,調(diào)用看看是不是符合預(yù)期就好
11.2、action
這個(gè)鏈接很棒:https://zhuanlan.zhihu.com/p/75513490
Action 應(yīng)對(duì)起來(lái)略微棘手疗隶,因?yàn)樗鼈兛赡苄枰{(diào)用外部的 API躬柬。當(dāng)測(cè)試 action 的時(shí)候,我們需要增加一個(gè) mocking 服務(wù)層——例如抽减,我們可以把 API 調(diào)用抽象成服務(wù)允青,然后在測(cè)試文件中用 mock 服務(wù)回應(yīng) API 調(diào)用。為了便于解決 mock 依賴卵沉,可以用 webpack 和 inject-loader 打包測(cè)試文件颠锉。
12、熱加載
對(duì)于 mutation 和模塊史汗,你需要使用 store.hotUpdate() 方法