Vuex
Vuex是一個專門為Vue.js應用所設計的集中式狀態(tài)管理架構嘉竟,它借鑒了Flux和Redux的設計思想,但簡化了概念洋侨,采用了一種為能更好地發(fā)揮Vue.js數(shù)據(jù)相應機制而專門設計的實現(xiàn)舍扰。
Vuex是一個專為Vue.js應用開發(fā)的狀態(tài)管理模式,采用集中式存儲管理應用組件狀態(tài)凰兑,并以響應規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化妥粟。
# 安裝Vuex
$ npm i vuex --S
# 引入
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
狀態(tài)管理模式
# 計數(shù)器
new Vue({
// state 驅動應用的數(shù)據(jù)源
data () {
return {
count:0
}
},
// view 以聲明方式將數(shù)據(jù)源映射到視圖
template:`<span>{{count}}</span>`,
//actions 響應在視圖上的用戶輸入導致的中狀態(tài)變化
methods:{
increment() {
this.count ++
}
}
})
狀態(tài)state
的概念,簡單來說可視為項目中使用的數(shù)據(jù)的集合吏够。Vuex使組件本地狀態(tài)和應用層級狀態(tài)有了一定的差異勾给。
- 組件本地狀態(tài):表示僅僅在組件內(nèi)部使用的狀態(tài),類似于通過配置選項傳入Vue組件內(nèi)部锅知。
- 應用層級狀態(tài):表示同時被多個組件共享的狀態(tài)層級
單向數(shù)據(jù)流
假如有一個父組件同時包含兩個子組件播急,父組件可以很容易的使用prop
屬性向子組件傳遞數(shù)據(jù)。兩個子組件如何和對象互相通信呢售睹?子組件如何傳遞數(shù)據(jù)給父組件呢桩警?項目規(guī)模很小時,可通過事件派發(fā)和監(jiān)聽來完成父組件和子組件的通信昌妹。隨著項目規(guī)模增長捶枢,遇到的問題是:
- 保持對所有事件追蹤變得越來越困難,到底哪個事件是哪個組件派發(fā)的飞崖,哪個組件該監(jiān)聽哪個事件呢烂叔?
- 項目邏輯分散在各個組件當中,很容易導致邏輯混亂固歪,不利于項目維護蒜鸡。
- 父組件變得和子組件耦合度越來越嚴重,因為它需要明確的派發(fā)和監(jiān)聽子組件的某些事件牢裳。
當應用遇到多個組件共享狀態(tài)時逢防,單向數(shù)據(jù)流的間接性很容易被破壞:
- 多個視圖依賴于同一個狀態(tài)
傳參的方法對于多層嵌套的組件將會非常繁瑣,對于兄弟組件間的狀態(tài)傳遞無能為力蒲讯。 - 來自不同視圖的行為需要變更同一狀態(tài)
采用父子組件直接飲用或通過事件來變更和同步狀態(tài)的多份拷貝
為什么不把組件的共享狀態(tài)抽取出來以一個全局單例模式管理呢忘朝?在這種模式下,組件樹構成一個巨大的視圖伶椿,不管在樹的哪個位置辜伟,任何組件都能獲取狀態(tài)或觸發(fā)行為氓侧。
另外,通過定義和隔離狀態(tài)管理中各種概念并強制遵守一定的規(guī)則导狡,代碼將會變得更加結構化且易于維護约巷。
核心概念
- 單一狀態(tài)樹
使用一個對象包含全部應用層級狀態(tài),它作為唯一數(shù)據(jù)源存在旱捧,這意味著独郎,每個應用將緊緊包含一個倉庫store
的實例。單一狀態(tài)樹讓我們能夠直接地定位任意特定的狀態(tài)片段枚赡,在調(diào)用過程中也能輕易地取得整個當前應用狀態(tài)的快照氓癌。 - 獲取狀態(tài)
派生狀態(tài)getters
用來從倉庫store
中獲取Vue組件數(shù)據(jù) - 狀態(tài)變更
狀態(tài)變更mutators
的事件處理器可用來驅動狀態(tài)的變化 - 異步操作
異步操作actions
可給組件使用的函數(shù),以此用來驅動事件處理器mutations
贫橙。
Vuex規(guī)定贪婉,屬于應用層級的狀態(tài)只能通過狀態(tài)變更mutation
中的方法來修改,而派發(fā)mutation
中的事件只能通過action
卢肃。
從左至右疲迂,從組件觸發(fā),組件中調(diào)用action
莫湘,在action
層可以和后臺數(shù)據(jù)交互尤蒿,比如獲取初始化數(shù)據(jù)源,或者中間數(shù)據(jù)的過濾等幅垮。然后在action
中去派發(fā)mutation
腰池。mutation
去觸發(fā)狀態(tài)的改變,從而觸發(fā)視圖的更新忙芒。
注意
- 數(shù)據(jù)流都是單向的
- 組件能夠調(diào)用
action
-
action
用來派發(fā)mutation
- 只有
mutation
可以改變狀態(tài) -
store
是響應式的示弓,無論state
什么時候更新,組件都將同步更新呵萨。
開始
每個Vuex應用的核心就是倉庫(store)避乏,store基本就是一個容器,包含著應用中大部分的狀態(tài)(state)甘桑。Vuex和單純的全局對象由兩點不同:
- Vuex的狀態(tài)存儲時響應式的,當Vue組件從store中讀取狀態(tài)時歹叮,若store中的狀態(tài)發(fā)生變化跑杭,相應的組件也會得到高效更新。
- 不能直接改變store中的狀態(tài)咆耿,改變store中的狀態(tài)唯一途徑就是顯式地提交(commit)mutation德谅。這樣使得我們可以方便地跟蹤每一個狀態(tài)的變化。
倉庫
Vuex應用狀態(tài)state
都存放在store
中萨螺,Vue組件可從store
中獲取狀態(tài)窄做,可把store
通俗的理解為一個全局變量的倉庫愧驱。和單純的全局變量的區(qū)別是當store
中的狀態(tài)發(fā)生變化時,相應的Vue組件也會得到更新椭盏。
//倉庫
const store = new Vuex.Store({
//狀態(tài)
state:{
count:0
},
//狀態(tài)變更
mutations:{
increment(state){
state.count++
}
}
})
//獲取狀態(tài)對象
store.state.count
//觸發(fā)狀態(tài)變更
store.commit('increment')
狀態(tài)
單一狀態(tài)樹
Vuex使用單一狀態(tài)樹组砚,用一個對象包含了全部應用層級狀態(tài),并作為一個“唯一數(shù)據(jù)源(SSOT)”而存在掏颊。這意味著糟红,每個應用將僅僅包含一個狀態(tài)實例。單一狀態(tài)樹讓我們能夠直接地定位任意特定的狀態(tài)片段乌叶,在調(diào)試過程中也能輕易地去的整個當前應用狀態(tài)的快照盆偿。
在Vue組件中獲得Vuex狀態(tài)
由于Vuex的狀態(tài)存儲的是響應式的,從store實例中讀取狀態(tài)最簡單是方式是在計算屬性中返回某個狀態(tài)准浴。
// 創(chuàng)建組件
const Counter = {
template:`<div>{{count}}</div>`,
computed:{
count () {
// 每當store.state.count變化時會重新求取計算屬性并觸發(fā)更新相關聯(lián)的DOM
return store.state.count
}
}
}
這種模式導致組件依賴全局狀態(tài)單例事扭,在模塊化的構建系統(tǒng)中,在每個需要使用state的組件中需要頻繁地導入乐横,并在測試組件時需模擬狀態(tài)求橄。
Vuex通過store選項,提供了一種機制將狀態(tài)從根組件注入到每個子組件中晰奖。
const app = new Vue({
el:'#app',
store,//將store對象提供給store選項谈撒,把store實例注入到所有的子組件中。
componenets:{Counter},
template:`<div class="app"><counter></counter></div>`
})
通過在根實例中注冊store選項匾南,該store實例會注入到根組件下的所有子組件中啃匿,且子組件能通過this.$store訪問到。
const Counter = {
template:`<div>{{count}}</div>`,
computed:{
count () {
return this.$store.state.count
}
}
}
當一個組件需要獲取多個狀態(tài)時蛆楞,將這些狀態(tài)都聲明為計算屬性會有些重復和冗余溯乒,為解決這個問題,使用mapState輔助函數(shù)幫助我們生成計算屬性豹爹。
// 單獨構建版本中輔助函數(shù)為Vuex.mapState
import {mapState} from 'vuex'
export default {
computed:mapState({
//箭頭函數(shù)使得代碼更簡潔
count:state=>state.count,
//傳字符串count等同于state=>state.count
countAlias:'count',
//為了能夠使用this獲取局部狀態(tài)必須使用常規(guī)函數(shù)
countPlusLocalState(state){
return state.count + this.localCount
}
})
}
當映射的計算屬性的名稱和state的子節(jié)點名稱相同時裆悄,也可以給mapState傳入一個字符串數(shù)組。
// 映射this.count為store.state.count
computed:mapState(['count'])
mapState函數(shù)返回的是一個對象臂聋,如何將其與局部計算屬性混合使用呢光稼?通常需要使用一個工具函數(shù)將多個對象合并為一個,以便于將最終對象傳給computed屬性孩等。自從有了對象展開運算符可極大地簡化寫法艾君。
computed:{
localComputed () {
// 使用對象展開運算符對此對象混入到外部對象中
...mapState({
//...
})
}
}
使用Vuex并不意味著需要將所有狀態(tài)放入Vuex,雖然將所有狀態(tài)放入Vuex會使狀態(tài)變化更顯式和易調(diào)用肄方,但也會使代碼變得冗長且不直觀冰垄。有些狀態(tài) 嚴格屬于單個組件,最好還是作為組件的局部狀態(tài)权她。
getter
有時需從倉庫中的狀態(tài)中派生一些狀態(tài)
computed:{
// 對列表進行過濾并計數(shù)
doneTodosCount() {
return this.$store.state.todos.filter(todo=>todo.done).length
}
}
如果有多個組件需要用到此屬性,要么復制函數(shù)或抽取到一個共享函數(shù)然后再多出導入,但是無論哪種方式都不是很理想。
Vuex允許在倉庫中定義getter(可認為是store的計算屬性),就像計算屬性一樣,getter的返回值會根據(jù)它的依賴被緩存起來,只有當當它的依賴發(fā)生變化了才會被重新計算。
const store = new Vuex.Store({
state:{
todos:[
{id:1, text:'...', done:true},
{id:2, text:'...', done:false}
]
},
// getter類似倉庫的計算屬性一樣做个,其返回值會根據(jù)其依賴被緩存起來太闺,只有當以來之發(fā)生改變才會被重新計算最住。
getters:{
doneTodos:state=>{
return state.todos.filter(todo=>todo.done)
}
}
})
getter會暴露為store.getters對象甚疟,可以以屬性的形式訪問其值:
# getter會暴露為store.getters對象揽祥,可以以屬性的形式訪問其值:
store.getters.doneTodos
# getter也可以接受其他getter作為第二個參數(shù)
getters:{
doneTodosCount:(state, getters)=>{
return getters.doneTodos.length
}
}
store.getters.doneTodosCount
# 可以很容易在任何組件中使用它
computed:{
doneTodosCount(){
return this.$store.getters.doneTodosCount
}
}
注意:getter在通過屬性訪問時是作為Vue的響應式系統(tǒng)的一部分緩存其中的料按。
可以通過讓getter返回一個函數(shù)來實現(xiàn)給getter傳參烹卒,在對store中的數(shù)組進行查詢時非常有用逢勾。
getters:{
//可以通過讓getter返回一個函數(shù)來實現(xiàn)給getter傳參
getTodoById:(state)=>(id)=>{
return state.todos.find(todo=>todo.id === id)
}
}
store.getters.getTodoById(2)
注意,getter在通過方法訪問時藐吮,每次都會去進行調(diào)用溺拱,而不會緩存結果。
mapGetters 輔助函數(shù)
# mapGetters輔助函數(shù)僅僅是將store中的getter映射到局部計算屬性
import {mapGetters} from 'vuex'
export default {
computed:{
//使用對象展開運算符將getter混入computed對象中
...mapGetters(['doneTodosCount','anotherGetter'])
}
}
# 若想將一個getter屬性另取一個名字炎码,使用對象形式盟迟。
mapGetters({
//將this.doneCount映射為this.$store.getters.doneTodosCount
doneCount:'doneTodosCount'
})
mutation
更改Vuex倉庫中的狀態(tài)的唯一方式是提交mutation。vuex中的mutation非常類似于事件:每個mutation都有一個字符串的事件類型(type)和一個回調(diào)函數(shù)(handler)潦闲。這個回調(diào)函數(shù)就是實際進行狀態(tài)更改的地方攒菠,并且它會接收state作為第一個參數(shù)。
const store = new Vuex.Store({
//狀態(tài)
state:{count:1},
//狀態(tài)變更
mutations:{
//每個mutation都有一個字符串的事件類型和一個回調(diào)函數(shù)
increment (state) {
state.count++ //變更狀態(tài)
}
}
})
不能直接調(diào)用一個mutation handler歉闰,這個選項更像是事件注冊:“當觸發(fā)一個類型為increment的mutation時辖众,調(diào)用此函數(shù)”。要喚醒一個mutation handler和敬,你需要以相應的type調(diào)用store.commit方法凹炸。
store.commit('increment')
提交載荷
可以像store.commit傳入額外的參數(shù),即mutation的載荷payload
mutations:{
increment (state, n){
state.count += n
}
}
// 向store.commit傳入額外的參數(shù)及mutation的載荷
store.commit('increment', 10)
多數(shù)情況下昼弟,載荷是一個對象啤它,可包含多個字段并記錄mutation會更易讀。
mutations:{
increment(state, payload) {
state.count += payload.amount
}
}
// 載荷為對象
store.commit('increment',{acount:10})
對象風格的提交方式
提交mutation的另一種方式是直接使用包含type屬性的對象
# 使用包含type屬性的對象提交mutation
store.commit({type:'increment', amount:10})
# 使用對象風格提交方式,整個對象都作為載荷傳給mutation函數(shù)变骡,因此handler保持不變离赫。
mutations:{
increment(state, payload){
state.count += payload.amount
}
}
mutation需遵守vue的響應規(guī)則
既然vuex的store中的狀態(tài)是響應式的,當變更狀態(tài)時塌碌,監(jiān)視狀態(tài)的vue組件會也自動更新渊胸,這也意味著vuex中的mutation也需要與使用vue一樣遵守一些事項:
- 最好提前在store中初始化好所有屬性
- 當需要在對象上添加新屬性時,應該
- 使用Vue.set(obj, 'prop', 123)
- 以新對象替換老對象
state.obj = {...state.obj, prop:123}
使用常量替代mutation事件類型
使用常量替代mutation事件類型在各種flux實現(xiàn)中最為常見台妆,可使用linter之類的工具發(fā)揮作用翎猛,同時將常量放在單獨的文件中可讓代碼合作者對整個app包含的mutation一目了然。
// mutation-types.js
export const SOME_MUTATION = ''
//store.js
import Vuex from 'vuex'
import {SOME_MUTATION} from './mutation-types'
const store = new Vuex.Store({
state:{...},
mutations:{
//使用ES2015風格的計算屬性命名功能來使用一個常量作為函數(shù)名稱
[SOME_MUTATION](state){
}
}
})
mutation必須是同步函數(shù)
Axios
Axios是一個基于promise的HTTP庫接剩,可用于瀏覽器和Node.js中切厘。
- 從瀏覽器中創(chuàng)建XMLHttpRequest
- 從Node.js中創(chuàng)建HTTP請求
- 支持Promise API
- 攔截請求和響應
- 轉換請求數(shù)據(jù)和響應數(shù)據(jù)
- 取消請求
- 自動轉換JSON數(shù)據(jù)
- 客戶端支持防御XSRF
# 安裝 axios
$ npm i axios -S
# 執(zhí)行GET請求
axios.get('/user?id=1').then(function(response){
console.log(response);
}).catch(function(error){
console.log(error)
})
axios.get('/user', {params:{id:1}}).then(function(response){
console.log(response);
}).catch(function(error){
console.log(error)
})
# 執(zhí)行POST請求
axios.post('/user',{username:'', password:''}).then(function(response){
console.log(response);
}).catch(function(error){
console.log(error)
})
# 執(zhí)行多個并發(fā)請求
function getUsername(){
return axios.get('/user/1')
}
function getPermission(){
return axios.get('/user/1/permission')
}
axios.all([getUsername(), getPermission()]).then(axios.spread(function(username, permission){
}))
axios API
# 向axios傳遞相關配置來創(chuàng)建請求
axios(url[, config])
Vue中使用Axios
Axios并非Vue的插件無法直接引入后使用,只能在每個需要發(fā)送請求的組件中即時引入搂漠。為了解決這個問題迂卢,有兩種思路:
- 引入Axios修改原型鏈
# main.js
# 引入
import axios from 'axios`
# 原型鏈綁定
Vue.prototype.$ajax = axios
# 使用
methods:{
submit(){
this.$ajax({
method:'post',
url:'url',
data:{username:'username', password:'password'}
})
}
}
- 結合Vuex封裝action
Vuex的mutation類似于事件,可用于提交Vuex中的狀態(tài)桐汤,action和mutation很類似而克,區(qū)別在于action包含異步操作,還可通過action來提交mutation怔毛。最主要的區(qū)別在于
- mutation 固有參數(shù)state用于接收Vuex中的state對象
- action 固有參數(shù)context是state的父級员萍,包含著state、getters拣度。