關(guān)于vuex,其實(shí)17年大概3 4 月份或許更早,在喻導(dǎo)那里就學(xué)習(xí)了相關(guān)概念,當(dāng)時學(xué)習(xí)熱情不高,約等于沒有,那個時候vue是個什么都不太清楚,只想好好學(xué)習(xí)jquery,這一年多跌跌撞撞的學(xué)過來,從拖延癥中擺脫出來再看vuex的官方文檔,結(jié)合自己平時碰到的問題,才有一種它確實(shí)是順勢而生的感覺,以下是我作為一個前端菜鳥現(xiàn)階段第一次認(rèn)真看文檔然后對vuex的理解,先記錄一下吧。
Vuex 是什么?
官方文檔上寫的是:
Vuex 是一個專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。Vuex 也集成到 Vue 的官方調(diào)試工具 devtools extension,提供了諸如零配置的 time-travel 調(diào)試、狀態(tài)快照導(dǎo)入導(dǎo)出等高級調(diào)試功能。
我對這里的理解是:數(shù)據(jù)管理,比如一個網(wǎng)站,你的作為用戶來說,你的名字/名稱和頭像是一個公共數(shù)據(jù),會在網(wǎng)站的很多個頁面/組件用到,既代碼里的很多組件里用到這一條數(shù)據(jù),如果你沒有一個“狀態(tài)管理器”,那么你在每個頁面都要向接口請求一次數(shù)據(jù),再給頁面上的相應(yīng)數(shù)據(jù)賦值,這樣就很麻煩。
文檔上對這種情況的解釋:
當(dāng)我們的應(yīng)用遇到多個組件共享狀態(tài)時,單向數(shù)據(jù)流的簡潔性很容易被破壞:
1.多個視圖依賴于同一狀態(tài)。
2.來自不同視圖的行為需要變更同一狀態(tài)。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態(tài)傳遞無能為力。對于問題二,我們經(jīng)常會采用父子組件直接引用或者通過事件來變更和同步狀態(tài)的多份拷貝。以上的這些模式非常脆弱,通常會導(dǎo)致無法維護(hù)的代碼。
因此,我們?yōu)槭裁床话呀M件的共享狀態(tài)抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構(gòu)成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態(tài)或者觸發(fā)行為!
另外,通過定義和隔離狀態(tài)管理中的各種概念并強(qiáng)制遵守一定的規(guī)則,我們的代碼將會變得更結(jié)構(gòu)化且易維護(hù)。
State
而在vuex里,你可以將數(shù)據(jù)放在一個store(倉庫)的state里,要在某個組件里用的話直接拿就行了
如果你有一個數(shù)據(jù)xx要在很多個組件中使用,那么先在index.html同級的地方新建一個store文件夾,再在里面建一個index.js文件
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 告訴 vue “使用” vuex
Vue.use(Vuex)
// 創(chuàng)建一個對象來保存應(yīng)用啟動時的初始狀態(tài)
// 需要維護(hù)的狀態(tài)
const store = new Vuex.Store({
state: {
// 放置初始狀態(tài) app啟動的時候的全局的初始值
data: {"xx":"我是vuex的第一個數(shù)據(jù)","id":100}
}
})
// 整合初始狀態(tài)和變更函數(shù),我們就得到了我們所需的 store
// 至此,這個 store 就可以連接到我們的應(yīng)用中
export default store
注:在計算屬性(computed)中返回某個狀態(tài)
// 你創(chuàng)建的 Counter 組件需要讀取xx數(shù)據(jù)
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.data.xx
}
}
}
每當(dāng) store.state.xx變化的時候, 都會重新求取計算屬性,并且觸發(fā)更新相關(guān)聯(lián)的 DOM。
為了避免頻繁導(dǎo)入state,你可以在根組件(app.vue)中將state注入到每一個子組件中,
const app = new Vue({
el: '#app',
// 把 store 對象提供給 “store” 選項,這可以把 store 的實(shí)例注入所有的子組件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
并且在main.js里加上
import store from './../store/index'
new Vue({
el: '#app',
router,
store,//加上
components: { App },
template: '<App/>'
})
Getter
計算屬性 ,這個很好理解,如果你某一個組件里需要獲取state里的數(shù)據(jù)并且需要過濾一下才能展示,可以用上這個,官網(wǎng)例子
store里的index.js
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
}
})
你的某個組件
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
同樣的,如果多個組件展示該數(shù)據(jù)都需要過濾,Vuex 允許我們在 store 中定義“getter”(可以認(rèn)為是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當(dāng)它的依賴值發(fā)生了改變才會被重新計算。
Getter 接受 state 作為其第一個參數(shù):
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 }]
訪問
Mutation
更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation,也就是說如果你想更改state里的數(shù)據(jù),你就需要提交mutation,每個 mutation 都有一個字符串的事件類型 (type) 和 一個 回調(diào)函數(shù) (handler)。這個回調(diào)函數(shù)就是我們實(shí)際進(jìn)行狀態(tài)更改的地方,并且它會接受 state 作為第一個參數(shù):
官網(wǎng)例子
store里的index.js
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) { //increment就是上面提到的事件類型,并且在這個函數(shù)里進(jìn)行狀態(tài)更改
// 變更狀態(tài)
state.count++
}
}
})
在你的某個組件里調(diào)用
store.commit('increment') //提交mutation,更改state數(shù)據(jù)
至于官方完檔里寫的關(guān)于 “不能直接調(diào)用一個 mutation handler”,這里我的理解是為了使用的時候能多樣的進(jìn)行數(shù)據(jù)變更吧。
就像一個簡單的關(guān)注功能,你點(diǎn)擊關(guān)注的話
store.commit('increment') //在type為increment的函數(shù)里讓關(guān)注的數(shù)據(jù)加一
如果你點(diǎn)擊取消關(guān)注
store.commit('decrement') //在type為decrement的函數(shù)里讓關(guān)注的數(shù)據(jù)減一
這樣你也可以在vue的調(diào)試devtools里更直觀的看到數(shù)據(jù)變化,從而調(diào)試你的代碼。
當(dāng)然這是最簡單的使用場景。
你可以向 store.commit 傳入額外的參數(shù)實(shí)現(xiàn)更復(fù)雜的邏輯,即 mutation 的 載荷(payload),具體參考官方文檔,傳送門:https://vuex.vuejs.org/zh/guide/mutations.html
Action
這個其實(shí)現(xiàn)在看還沒有很理解,先說一下對他淺淺的理解吧:
因為mutation的改變是同步的,當(dāng)涉及異步操作(比如要修改一個表單里的數(shù)據(jù),點(diǎn)擊提交修改的時候必須先把數(shù)據(jù)傳給后端,成功之后再來修改state里的數(shù)據(jù)),狀態(tài)則不會改變。
所以這時候需要action來配合使用,用action來提交mutation從而修改state(狀態(tài)),這里需要注意:
action本身不會修改狀態(tài),而是提交mutation,它沒有同步的限制。
注冊一個簡單的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 函數(shù)接受一個與 store 實(shí)例具有相同方法和屬性的 context 對象,因此你可以調(diào)用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。
Action 通過 store.dispatch 方法觸發(fā):
在需要觸發(fā)action的地方
this.$store.dispatch('increment')//觸發(fā)action提交type為increment的mutation從而改變狀態(tài)
一個 store.dispatch 在不同模塊中可以觸發(fā)多個 action 函數(shù),這是一個action:
actions: {
actionA ({ commit }) { //這個Promise執(zhí)行完成之后提交type為someMutation的mutation
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
你可以:
store.dispatch('actionA').then(() => { //在actionA完成后dosomething
// ...dosomething
})
在另外一個 action 中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
觸發(fā)actionB的話,觸發(fā)actionA=>執(zhí)行A中的Promise=>提交type為someMutation的mutation =>A完成=>提交type為someOtherMutation的mutation =>B完成。
(感覺就是在一個回調(diào)里放另一個回調(diào)吧)
這次認(rèn)真的看文檔就這些感受了
其他:文檔上還有一些別的什么比如:
mapState,mapGetters,mapMutations,mapActions這些輔助函數(shù),省去一些代碼量,寫出來更簡潔
Module的概念,當(dāng)應(yīng)用變得非常復(fù)雜時,store 對象就有可能變得相當(dāng)臃腫。Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進(jìn)行同樣方式的分割:
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)
等等其他一些更詳細(xì)的知識點(diǎn)要根據(jù)自己上手情況再結(jié)合文檔仔細(xì)琢磨一下
ps:如果有大佬看到這篇文章并發(fā)現(xiàn)有什么理解不正確的地方歡迎指正。
官方文檔再次傳送門:https://vuex.vuejs.org/zh/