數據管理
在實際的開發中,經常會遇到多個組件共享一個數據的場景
面對這種場景,會產生至少以下兩個問題:
- 多個組件如何共享同一份數據?
- 如果某個組件修改了數據,如何讓其他組件知道?
面對這種問題,一個可行的解決辦法,就是讓數據提升
所謂數據提升,就是把數據提升到更加頂層的組件,讓頂層的組件通過屬性下發數據,而當組件想改變數據的時候,又通過事件一層層向上傳遞
使用這種方式,雖然可以解決問題,但是帶來了更多的問題:
- 書寫特別繁瑣
- 依賴極其混亂:某些組件本來並不需要一些數據,但是由於它的子組件需要,自己也必須要接收
- 無謂的重新渲染:如果數據變化了,並不依賴這些數據的組件也會被迫重新渲染
爲了解決這些問題,vuex
出現了
vuex
專門用於解決共享數據問題,它的思路和上述一致,也是將數據提升到頂層,不過它使用了一些特別的技巧,不僅讓組件的依賴更加清晰,當數據變動時,僅衝渲染依賴該數據的組件
但是要注意,並非所有數據都需要讓vuex
管理,通常vuex只管理那些需要被組件共享的數據
在實際的開發中,一些邏輯特別複雜的數據,儘管不共享,也可能提取到vuex中進行管理
安裝
在頁面中引入vuex
庫
該庫提供了一個構造函數Vuex.Store
,通過該構造函數,即可創建一個數據倉庫
var store = new Vuex.Store({
// 倉庫數據配置
})
將得到的對象,配置到vue
中,即可在vue
中注入vuex
的功能
new Vue({
// 其他配置
store
})
現在,你的vue
應用擁有了使用數據倉庫的能力
初始化狀態
vuex
將倉庫中的數據稱之爲state 狀態
在vuex
的配置中使用屬性state
即可配置倉庫中的狀態初始值
var store = new Vuex.Store({
state: { // 狀態初始值,這些數據可以被所有組件共享
onlineNumber: 0, // 在線人數
movies: { // 電影數據
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
}
}
})
在組件中使用該數據非常簡單
vuex
向vue實例
中注入了一個屬性$store
,通過該屬性即可得到倉庫中的數據
this.$store.state // 倉庫中的狀態
<h1>
在線人數:{{$store.state.onlineNumber}}
</h1>
雖然這種寫法看上去很方便,但是書寫繁瑣,並且不利於從閱讀上明確依賴關係
更多的時候,我們會將組件對數據的依賴,明確的聲明在組件的computed
配置中
var Comp = {
computed:{
onlineNumber(){
return this.$store.state.onlineNumber;
}
},
template: `
<h1>
在線人數:{{onlineNumber}}
</h1>
`
}
爲了更加方便的讓我們書寫computed
,vuex
提供了一個函數vuex.mapState
var Comp = {
computed: Vuex.mapState(["onlineNumber"]), // 等同於上面的代碼
template: `
<h1>
在線人數:{{onlineNumber}}
</h1>
`
}
vuex.mapState
有非常非常多的用法,目的只有一個:更加方便的書寫computed
狀態模塊化
通常情況下,一個vue實例,只有一個數據倉庫
如果倉庫中的所有狀態都放在一起,既不利於管理,也容易產生名稱的衝突
實際開發中,倉庫中的狀態往往是分爲多個模塊的
var movies = { // 電影模塊
state: { // 電影模塊的初始狀態
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
}
}
var online = { // 在線統計模塊
state: {
number: 0
}
}
// 合併狀態模塊
var store = new Vuex.Store({
modules:{
movies,
online
}
})
從此,store中的狀態如下:
{
online:{
number:0
},
movies:{
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
}
}
在使用時,可以通過下面的方式映射到computed
var Comp = {
computed: Vuex.mapState("online", ["number"]), // 第一個參數是模塊名稱(命名空間名)
template: `
<h1>
在線人數:{{number}}
</h1>
`
}
數據變化
數據不可能永遠不變,但數據也不會無緣無故的變化
每一次數據變化都有原因,在某種原因的驅使下,數據從一種狀態變化到另一種狀態
vuex
把數據的變化過程,稱之爲mutation
mutation
在代碼中表現爲一個函數,配置在mutations
中
var movies = { // 電影模塊
state: { // 電影模塊的初始狀態
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
},
mutations:{
/**
* setPage: mutation的名稱
* oldState: 原來的狀態
* payload: 載荷。爲了變化新狀態,需要的額外信息
*/
setPage(oldState, payload){
oldState.page = payload.page;
},
setResp(oldState, payload){
oldState.data = payload.data;
oldState.total = payload.total;
}
}
}
看上去,mutation似乎非常簡單,僅僅是完成一個賦值即可
但它的意義是非凡的
它至少向外表達了以下信息:
- 我的數據可以發生哪些變化
- 除了這些變化之外,不可能再發生任何其他變化
- 這些變化是原子性的,不可分割的。比如
setResp
,必須同時變化data
和total
,不可能分割
mutation的存在,讓狀態變化變得統一、可控
通過mutation觸發數據的變化,稱之爲commit 提交
提交mutation
,是數據發生變化的唯一原因
在代碼層面,提交mutation
通過下面的api完成
// 觸發setPage運行,payload爲2
this.$store.commit("setPage", {page:2});
this.$store.commit({
type: "setPage",
page: 2
})
當倉庫中的狀態變化時,所有依賴該狀態的組件都會自動重新渲染
追蹤數據變化
mutation
讓追蹤數據變化成爲了可能,這非常有利於調試
chrome
擴展程序vue-devtools
同時,這也要求我們在編寫mutation
時,要注意:
mutation 中不要出現異步代碼,否則會讓狀態的變化難以追蹤
啓用命名空間
由於數據模塊後,非常容易出現mutation
重名
啓用命名空間即可解決該問題
var movies = { // 電影模塊
namespaced: true, // 啓用命名空間
state: {
data: [],
total: 0,
page: 1,
limit: 3,
isLoading: false
},
mutations:{
setPage(oldState, payload){
oldState.page = payload.page;
},
setResp(oldState, payload){
oldState.data = payload.data;
oldState.total = payload.total;
}
}
}
命名空間啓用後,commit
時,mutation
的名稱前需要加上命名空間
this.$store.commit("movies/setPage", { page: 1 })
異步處理
由於mutation
不允許使用異步代碼,所以異步代碼需要單獨處理
vuex
把異步處理稱之爲action
在action
中不允許直接更改狀態,但允許提交mutation
var online = {
namespaced: true,
state:{
number: 0
},
mutations:{
add(state){
state.number++;
}
},
actions:{ //處理異步
asyncAdd(context){
// 可以把context當作是store對象
setTimeout(function(){
context.commit("add")
}, 1000)
}
}
}
在action
中,你可以通過參數context
參數,獲取store
實例
但值得注意的是,如果開啓了namespaced
,則context
和store
有以下不同:
context.state
是當前模塊的狀態,而不是整個倉庫的狀態context.rootState
纔是整個倉庫的狀態context.commit
可省略當前命名空間
如果要觸發一個action
,需要使用下面的API
this.$store.dispatch("online/asyncAdd");