目錄
- Vue組件間通信方式回顧
- 組件內(nèi)的狀態(tài)管理流程
- 組件間通信方式
- 父組件給子組件傳值 (最簡單的一種方式)
- 子組件給父組件傳值
- 不相關(guān)組件之間傳值
- 其他常見方式($ref)
- 簡易的狀態(tài)管理方案
- 上面組件間通信方式的問題
- 集中式的狀態(tài)管理方案
- Vuex
- 什么是Vuex?
- 什么情況下使用Vuex?
- 非必要的情況不要使用Vuex
- 中大型單頁應(yīng)用程序使用更好
- Vuex核心概念回顧
- Vuex基本結(jié)構(gòu)
- State的使用
- Getter的使用
- Mutation的使用
- Mutation的調(diào)試
- 時光旅行
- 狀態(tài)回滾
- 提交改變
- Mutation的調(diào)試
- Actions的使用
- Modules的使用
- 模塊定義
- 模塊注冊
- 模塊使用
- 添加命名空間
- Vuex嚴格模式
- Vuex插件
- 插件的使用
年底最后一天來本來沒有想更博客持灰,但是留下我今年還在努力學(xué)習(xí)的印記哈哈篓足。看了半天這居然是我第一個關(guān)于vue
的博客拐纱,努力不算晚泵督。先來對這個博客的內(nèi)容進行一下梳理趾盐,有興趣的小伙伴可以跳到自己感興趣的地方,這個博客比較長小腊,長文警告救鲤。
Vue組件間通信方式回顧
組件內(nèi)的狀態(tài)管理流程
Vue最核心的兩個功能:數(shù)據(jù)驅(qū)動 和 組件化
使用基于組件化的開發(fā),可以提高開發(fā)效率秩冈,帶來更好的可維護性本缠。
new Vue({
// state 組件內(nèi)部都可以管理自己的內(nèi)部狀態(tài)
data () {
return {
count: 0
}
},
// view 視圖,每個組件都有自己的視圖入问,把狀態(tài)綁定到視圖上丹锹,當用戶和視圖交互的時候,可能會更改狀態(tài)
template: `<div>{{ count }}</div>`,
// actions 行為芬失,這里描述的是單個組件內(nèi)部的狀態(tài)管理楣黍,實際開發(fā)中可能多個組件可以共享狀態(tài)
methods: {
increment () {
this.count++
}
}
})
這里說的狀態(tài)管理 —— 是通過狀態(tài),集中管理和分發(fā)棱烂,解決多個組件共享狀態(tài)的問題锡凝。
-
state
:狀態(tài),數(shù)據(jù)源垢啼。 -
view
:視圖。通過把狀態(tài)綁定到視圖呈現(xiàn)給用戶 -
actions
:用戶和視圖交互改變狀態(tài)的方式
圖中表明张肾,狀態(tài)綁定到視圖上呈現(xiàn)給用戶芭析,用戶通過與視圖交互改變狀態(tài),之后改變了的狀態(tài)再綁定到視圖會后呈現(xiàn)給用戶吞瞪。
單向的數(shù)據(jù)流程很簡單清晰馁启,但是多個組件共享數(shù)據(jù)會破壞這種簡單的結(jié)構(gòu)。
組件間通信方式的回顧
大多數(shù)情況下芍秆,組件都不是孤立存在的惯疙,他們需要共同協(xié)作構(gòu)成一個復(fù)雜的業(yè)務(wù)功能,在Vue
中妖啥,為不同的組件關(guān)系提供了不同的通信規(guī)則霉颠。
常見的組件間通信的方式有:
父組件給子組件傳值 (最簡單的一種方式)
- 父組件中給子組件通過相應(yīng)屬性傳值
- 子組件通過
props
接受數(shù)據(jù)
<!--子組件-->
<template>
<div>
<h1>Props Down Child</h1>
<h2>{{ title }}</h2>
</div>
</template>
<script>
export default {
// 子組件中通過props來接收父組件傳的值
// props可以是數(shù)組也可以是對象
// 如果想約定傳值的類型用對象,這里title定了是string類型荆虱,如果傳number類型會報錯
// props: ['title'],
props: {
title: String
}
}
</script>
<style>
</style>
<!--父組件-->
<template>
<div>
<h1>Props Down Parent</h1>
<!--2. 使用子組件的使用通過屬性給子組件傳值蒿偎,這里也可以是表達式朽们,綁定data中的成員-->
<child title="My journey with Vue"></child>
</div>
</template>
<script>
import child from './01-Child'
export default {
// 1. 注冊子組件
components: {
child
}
}
</script>
<style>
</style>
子組件給父組件傳值
- 子組件通過自定義事件,用
$emit
觸發(fā)的時候攜帶參數(shù)給父組件傳值 - 父組件通過
$on
注冊子組件內(nèi)部觸發(fā)的事件诉位,并接收傳遞的數(shù)據(jù)骑脱,行內(nèi)可以通過$event
獲取事件傳遞的參數(shù) (事件處理函數(shù)中是不這么使用的)
<!--子組件-->
<template>
<div>
<h1 :style="{ fontSize: fontSize + 'em' }">Props Down Child</h1>
<button @click="handler">文字增大</button>
</div>
</template>
<script>
export default {
// 通過props接收父組件傳的默認字體大小
props: {
fontSize: Number
},
methods: {
handler () {
// 當點擊按鈕的時候,觸發(fā)自定義事件enlargeText放大字體苍糠,讓字體放大0.1
// this是當前子組件對象叁丧,this.$emit這個是由子組件觸發(fā)的自定義事件,當注冊事件的時候要給子組件注冊該事件
this.$emit('enlargeText', 0.1)
}
}
}
</script>
<style></style>
<!--父組件-->
<template>
<div>
<!--父組件將fontSize進行綁定-->
<h1 :style="{ fontSize: hFontSize + 'em'}">Event Up Parent</h1>
這里的文字不需要變化
<!--使用子組件岳瞭,通過v-on給子組件設(shè)置了自定義方法enlargeText-->
<child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
<child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
<!--還有一種在行內(nèi)獲取值的方式拥娄,獲取自定義事件傳遞數(shù)據(jù)的時候,直接通過$event獲取寝优,這個值是觸發(fā)事件傳遞的0.1-->
<child :fontSize="hFontSize" v-on:enlargeText="hFontSize += $event"></child>
</div>
</template>
<script>
import child from './02-Child'
export default {
components: {
child
},
data () {
return {
hFontSize: 1
}
},
methods: {
// 子組件把值傳遞給了父組件条舔,父組件通過參數(shù)接收到了值,進行運算
enlargeText (size) {
this.hFontSize += size
}
}
}
</script>
<style></style>
不相關(guān)組件之間傳值
- 也是使用自定義事件的方式乏矾,但是因為沒有父子關(guān)系孟抗,所以不能通過子組件觸發(fā)傳值,所以這里需要使用
eventBus
钻心,即創(chuàng)建一個公共的Vue
實例凄硼,這個實例的作用是作為事件總線,或者事件中心捷沸。 -
eventBus
:創(chuàng)建一個Vue
實例摊沉,這個實例并不是用來展示內(nèi)容,所以沒有傳遞任何的選項痒给,我們使用他的目的是使用$emit
和$on
说墨,用來觸發(fā)和注冊事件。 - 觸發(fā)事件組件:通過
$emit
觸發(fā)事件苍柏,并傳遞參數(shù) - 注冊事件組件:通過
$on
注冊事件尼斧,接收參數(shù)進行處理
// eventbus.js
import Vue from 'vue'
export default new Vue()
<!--組件一:觸發(fā)事件-->
<template>
<div>
<h1>Event Bus Sibling01</h1>
<div class="number" @click="sub">-</div>
<input type="text" style="width: 30px; text-align: center" :value="value">
<div class="number" @click="add">+</div>
</div>
</template>
<script>
// 這個組件要觸發(fā)事件,將事件中心導(dǎo)入
import bus from './eventbus'
export default {
// props參數(shù)num
props: {
num: Number
},
// 因為props的值不能隨便改動试吁,所以傳遞給value
created () {
this.value = this.num
},
data () {
return {
value: -1
}
},
methods: {
// 減值操作棺棵,判斷不能為0
sub () {
if (this.value > 1) {
this.value--
// 觸發(fā)bus的自定義事件numchange,并把value當參數(shù)傳遞出去
bus.$emit('numchange', this.value)
}
},
// 加值操作熄捍,和減值類似
add () {
this.value++
bus.$emit('numchange', this.value)
}
}
}
</script>
<style>
.number {
display: inline-block;
cursor: pointer;
width: 20px;
text-align: center;
}
</style>
<!--組件二:定義-->
<template>
<div>
<h1>Event Bus Sibling02</h1>
<div>{{ msg }}</div>
</div>
</template>
<script>
// 因為要注冊事件烛恤,所以將事件中心導(dǎo)入
import bus from './eventbus'
export default {
data () {
return {
msg: ''
}
},
created () {
// 通過bus注冊了numchange事件,事件處理函數(shù)中接收事件觸發(fā)時候傳遞的參數(shù)余耽,進行展示
bus.$on('numchange', (value) => {
this.msg = `您選擇了${value}件商品`
})
}
}
</script>
<style>
</style>
<!--App.vue-->
<template>
<div id="app">
<h1>不相關(guān)組件傳值</h1>
<sibling0301 :num="num"></sibling0301>
<sibling0302></sibling0302>
</div>
</template>
<script>
import sibling0301 from './components/03-event-bus/03-Sibling-01'
import sibling0302 from './components/03-event-bus/03-Sibling-02'
export default {
name: 'App',
components: {
sibling0301,
sibling0302,
},
data () {
return {
num: 1
}
}
}
</script>
<style></style>
其他常見方式
-
$root
缚柏,$parent
,$children
宾添,$ref
船惨,通過這幾種屬性獲取根組件成員柜裸,實現(xiàn)組件之間的通信。但這些都是不被推薦的方式粱锐。只有當項目很小疙挺,或者在開發(fā)自定義組件的時候,才會使用到怜浅。如果是大型項目的話铐然,還是推薦使用Vuex管理狀態(tài)。
下面舉例通過$refs
獲取子組件的狀態(tài)恶座,其他屬性可以自己查看文檔搀暑。
ref
的兩個作用
- 在普通
HTML
標簽上使用ref
,用$refs
獲取到的是DOM
對象- 在組件標簽上使用
ref
跨琳,用$refs
獲取到的是組件實例
<!--子組件自点,一個簡單的自定義組件,功能是能夠獲取焦點的自定義文本框脉让。-->
<template>
<div>
<h1>ref Child</h1>
<!--這個input標簽上設(shè)置了ref屬性-->
<input ref="input" type="text" v-model="value">
</div>
</template>
<script>
export default {
data () {
return {
value: ''
}
},
methods: {
focus () {
// 通過this.$refs.input獲取input的DOM對象桂敛,并調(diào)用其focus方法讓文本框獲取焦點
this.$refs.input.focus()
}
}
}
</script>
<style></style>
<!--父組件,-->
<template>
<div>
<h1>ref Parent</h1>
<!--在子組件的標簽上設(shè)置了ref-->
<child ref="c"></child>
</div>
</template>
<script>
import child from './04-Child'
export default {
components: {
child
},
mounted () {
// 這里想要拿到子組件的話溅潜,必須要等組件渲染完畢术唬,所以這里在mounted函數(shù)下
// 這里通過this.$refs.c就是子組件對象,拿到這個對象就可以訪問其屬性和方法
// 這里調(diào)用子組件方法讓其內(nèi)部獲取焦點
this.$refs.c.focus()
// 通過value屬性給文本框賦值
this.$refs.c.value = 'hello input'
}
}
</script>
<style>
</style>
還是一句話不建議使用滚澜,如果濫用這種方式的話可以造成狀態(tài)管理的混亂粗仓。
簡易的狀態(tài)管理方案
上面組件間通信方式的問題
如果多個組件之間需要共享狀態(tài),使用之前演示的方式雖然都可以實現(xiàn)设捐,但是比較麻煩借浊,而且多個組件之間進行傳值,很難跟蹤到數(shù)據(jù)的變化萝招。如果出現(xiàn)問題的話巴碗,很難定位問題。當遇到多個組件需要共享狀態(tài)的時候即寒,典型的場景如購物車,我們使用之前介紹的方案都不合適召噩,可能會遇到以下的問題:
- 多個視圖依賴同一狀態(tài)母赵,如果多層嵌套的組件依賴同一狀態(tài),使用父子組件傳值可以實現(xiàn)具滴,但是非常麻煩而且不易管理凹嘲。
- 來自不同視圖的行為需要變更同一狀態(tài),我們可以通過父子組件的方式對狀態(tài)進行修改构韵,或者通過事件機制來改變周蹭,或者同步狀態(tài)的變化趋艘,以上這些方式非常的脆弱,通常會導(dǎo)致產(chǎn)生無法維護的代碼凶朗。
集中式的狀態(tài)管理方案
為了解決這些問題瓷胧,我們把不同組件的共享狀態(tài)抽取出來,存儲到一個全局對象中并且將來使用的時候保證其實響應(yīng)式的棚愤。這個對象創(chuàng)建好之后里面有全局的狀態(tài)和修改狀態(tài)的方法搓萧,我們的任何組件都可以獲取和通過調(diào)用對象中的方法修改全局對象中的狀態(tài) (組件中不允許直接修改對象的state
狀態(tài)屬性)。
把多個組件的狀態(tài)放到一個集中的地方存儲宛畦,并且可以檢測到數(shù)據(jù)的更改瘸洛,這里先不使用Vuex
,我們自己先進行一個簡單的實現(xiàn)次和。
- 創(chuàng)建一個全局的
store.js
集中式的狀態(tài)管理反肋,所有的狀態(tài)都在這里。這個模塊中導(dǎo)出了一個對象踏施,這對象就是狀態(tài)倉庫且全局唯一的對象石蔗,任何組件都可以導(dǎo)入這個模塊使用
這里面有
state
,還有actions
读规,state
是用來存儲狀態(tài)抓督,actions
是用戶交互更改視圖用的。還有一個debug
的屬性束亏,方便開發(fā)調(diào)試铃在。
// store.js
export default {
debug: true,
state: {
user: {
name: 'xiaomao',
age: 18,
sex: '男'
}
},
setUserNameAction (name) {
if (this.debug) {
console.log('setUserNameAction triggered:', name)
}
this.state.user.name = name
}
}
- 在組件中導(dǎo)入
<!--組件A-->
<template>
<div>
<h1>componentA</h1>
<!--3. 可以在視圖中直接用點的方式顯示數(shù)據(jù)-->
user name: {{ sharedState.user.name }}
<button @click="change">Change Info</button>
</div>
</template>
<script>
// 1. 在組件中導(dǎo)入store
import store from './store'
export default {
methods: {
// 4. 當點擊按鈕的時候,調(diào)用store的方法碍遍,將值改為componentA
change () {
store.setUserNameAction('componentA')
}
},
data () {
return {
// 當前組件還可以有自己的私有狀態(tài)定铜,存在privateState中
privateState: {},
// 2. 將store的state屬性賦值給shareState
sharedState: store.state
}
}
}
</script>
<style></style>
<!--組件B,用法與上面一樣怕敬,就是修改名字的時候值為componentB-->
<template>
<div>
<h1>componentB</h1>
user name: {{ sharedState.user.name }}
<button @click="change">Change Info</button>
</div>
</template>
<script>
import store from './store'
export default {
methods: {
change () {
// 修改名字的時候改成了componentB
store.setUserNameAction('componentB')
}
},
data () {
return {
privateState: {},
sharedState: store.state
}
}
}
</script>
<style></style>
上面組件A
和組件B
都共享了全局的狀態(tài)揣炕,并且用戶都可以更改狀態(tài)。調(diào)試的時候东跪,按A
組件的按鈕兩者都變成了componentA
畸陡,點B
組件的按鈕兩者都變成了componentB
。
我們不在組件中直接修改狀態(tài)的值而是通過調(diào)用store
的actions
來修改值虽填,這樣記錄的好處是: 能夠記錄store
中所以state
的變更丁恭,當可以實現(xiàn)記錄store
的state
的變更時候,就可以實現(xiàn)高級的調(diào)試功能斋日。例如:timeTravel
(時光旅行)和歷史回滾功能牲览。
剛才使用的store
,其實就類似于Vuex
的倉庫恶守。
當項目比較復(fù)雜第献,多個組件共享狀態(tài)的時候贡必,使用組件間通信的方式比較麻煩,而且需要維護庸毫。這個時候我們可以使用集中式的狀態(tài)解決方案 —— Vuex
Vuex
好的終于進入了主題~~
什么是Vuex?
- Vuex官網(wǎng)
-
Vuex
是專門為Vue.js
設(shè)計的狀態(tài)管理庫仔拟,從使用的角度其實就是一個JavaScript
庫 - 它采用集中式的方式存儲需要共享的數(shù)據(jù),如果狀態(tài)特別多的話不易管理岔绸,所以
Vuex
還提供了一種模塊的機制理逊,按照模塊管理不同的狀態(tài) - 它的作用是進行狀態(tài)管理,解決復(fù)雜組件通信盒揉,數(shù)據(jù)共享
-
Vuex
也集成到Vue
的官方調(diào)試工具devtools extension
晋被,提供了time-travel
時光旅行、歷史回滾刚盈、狀態(tài)快照羡洛、導(dǎo)入導(dǎo)出等高級調(diào)試功能
什么情況下使用Vuex?
非必要的情況不要使用Vuex
Vuex
可以幫助我們管理組件間共享的狀態(tài),但是在項目中使用Vuex
的話藕漱,我們需要了解Vuex
中帶來的新的概念和一些API
欲侮,如果項目不大,并且組件間共享狀態(tài)不多的情況下肋联,這個時候使用Vuex
給我們帶來的益處并沒有付出的時間多威蕉。此時使用簡單的 store 模式 或者其他方式就能滿足我們的需求。
中大型單頁應(yīng)用程序使用更好
中大型單頁應(yīng)用程序中橄仍,使用Vuex
可以幫我們解決多個視圖依賴同一狀態(tài)韧涨、來自不同視圖的行為需要變更同一狀態(tài)的問題。建議符合這些情況的業(yè)務(wù)侮繁,使用Vuex
進行狀態(tài)管理虑粥,會給我們提供更好的處理組件的狀態(tài),帶來的收益會更好些宪哩。例如典型案例:購物車娩贷。
注意:不要濫用
Vuex
,否則會讓業(yè)務(wù)變得更復(fù)雜锁孟。
Vuex核心概念回顧
下面這張圖展示了Vuex
的核心概念并且展示了Vuex
的整個工作流程
-
Store:倉庫彬祖,
Store
是使用Vuex
應(yīng)用程序的核心,每個應(yīng)用僅有一個Store
品抽,它是一個容器涧至,包含著應(yīng)用中的大部分狀態(tài),當然我們不能直接改變Store
中的狀態(tài)桑包,我們要通過提交Mutations
的方式改變狀態(tài)。 -
State:狀態(tài)纺非,保存在
Store
中哑了,因為Store
是唯一的赘方,所以State
也是唯一的,稱為單一狀態(tài)樹弱左,這里的狀態(tài)是響應(yīng)式的窄陡。 -
Getter:相當于
Vuex
中的計算屬性,方便從一個屬性派生出其他的值拆火,它內(nèi)部可以對計算的結(jié)果進行緩存跳夭,只有當依賴的狀態(tài)發(fā)生改變的時候,才會重新計算们镜。 -
Mutation:狀態(tài)的變化必須要通過提交
Mutation
來完成币叹。 -
Actions:與
Mutation
類似,不同的是可以進行異步的操作模狭,內(nèi)部改變狀態(tài)的時候都需要改變Mutation
颈抚。 -
Module:模塊,由于使用的單一狀態(tài)樹讓所有的狀態(tài)都會集中到一個比較大的對象中嚼鹉,應(yīng)用變得很復(fù)雜的時候贩汉,
Store
對象就會變得相當臃腫,為了解決這些問題Vuex
允許我們將Store
分割成模塊锚赤,每個模塊擁有自己的State
匹舞,Mutation
,Actions
线脚,Getter
赐稽,甚至是嵌套的子模塊。
Vuex基本結(jié)構(gòu)
使用vue-cli
創(chuàng)建項目的時候酒贬,如果選擇了Vuex
又憨,會自動生成Vuex
的基本結(jié)構(gòu)。
// store.js
import Vue from 'vue'
// 導(dǎo)入Vuex插件
import Vuex from 'vuex'
// 通過use方法注冊插件
// 插件內(nèi)部把Vuex的Store注入到了Vue的實例上
Vue.use(Vuex)
// 創(chuàng)建了Vuex的Store對象并且導(dǎo)出
export default new Vuex.Store({
state: {
...
},
mutations: {
...
},
actions: {
...
},
modules: {
...
}
// 如果有需要還可以有g(shù)etters
})
//App.js
// 導(dǎo)入store對象
import store from './store'
new Vue({
router,
// 在初始化Vue的時候傳入store選項锭吨,這個選項會被注入到Vue實例中
// 我們在組件中使用的this.$store就是在這個地方注入的
store,
render: h => h(App)
}).$mount('#app')
State的使用
- 下載項目模板vuex-sample-temp 蠢莺,
npm install
下載依賴,在store/index.js
中定義兩個state
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'hello vue'
},
mutations: {
},
actions: {
},
modules: {
}
})
- 在
App.vue
中使用state
零如,然后使用npm run serve
查看結(jié)果
<template>
<div id="app">
<h1>Vuex - Demo</h1>
count: {{ $store.state.count}} <br/>
msg: {{ $store.state.ms }}
</div>
</template>
<script>
- 每次使用變量都要前面寫
$store.state 很是麻煩躏将,所以這里使用``Vuex
內(nèi)部提供的myState
的函數(shù),會幫我們生成狀態(tài)對應(yīng)的計算屬性
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<!--4. 在使用的時候直接用計算屬性count和msg即可-->
count: {{ count }} <br/>
msg: {{ msg }}
</div>
</template>
<script>
// 1. 引入vuex的mapState模塊
import { mapState } from 'vuex'
export default {
// 2. 在計算屬性中調(diào)用mapState函數(shù)
computed: {
// 3. mapState需要接收數(shù)組作為參數(shù)考蕾,數(shù)組的元素是需要映射的狀態(tài)屬性
// 會返回一個對象祸憋,包含兩個對應(yīng)屬性計算的方法
// { count: state => state.count, msg: state => state.msg }
// 然后這里使用擴展運算符展開對象,完成之后我們就有了count和msg兩個計算屬性
...mapState(['count', 'msg'])
}
}
</script>
- 上面的方法比較簡潔但是如果這個組件中本身就有
count
或者msg
屬性肖卧,就會造成名稱沖突蚯窥,這個時候需要設(shè)置別名。
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<!-- 使用的時候直接使用別名即可 -->
count: {{ num }} <br/>
msg: {{ message }}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// mapState可以傳對象,鍵是別名拦赠,值是映射的狀態(tài)屬性
...mapState({ num: 'count', message: 'msg' })
}
}
</script>
Getter的使用
Vuex
中的getter
就相當于組件中的計算屬性巍沙,如果想要對state
的數(shù)據(jù)進行簡單的處理在展示,可以使用getter
這里用
Vuex
的getter
處理而不是用組件中的計算屬性是因為狀態(tài)本身屬于Vuex
荷鼠,應(yīng)該在其內(nèi)部處理
- 在
store.js
中設(shè)置getters
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'hello vue'
},
// 與計算屬性的寫法一致
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
},
actions: {
},
modules: {
}
})
- 在
App.vue
中使用
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<h2>reverseMsg: {{ $store.getters.reverseMsg }}</h2>
<br/>
</div>
</template>
- 同樣那樣引用過于麻煩句携,那么和
mapState
一樣,使用內(nèi)部的mapGetters
允乐,也是將其映射到組件的計算屬性矮嫉,其用法和mapState
一樣,也可以為了避免沖突使用對象設(shè)置別名牍疏。
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<h2>reverseMsg: {{ reverseMsg }}</h2>
<br/>
</div>
</template>
<script>
// 1. 引入vuex的mapGetters模塊
import { mapGetters } from 'vuex'
export default {
// 2. 在計算屬性中調(diào)用mapGetters函數(shù)
computed: {
// 3. 用法與mapState一致蠢笋,這里也可以使用對象設(shè)置別名
...mapGetters(['reverseMsg'])
}
}
</script>
Mutation的使用
狀態(tài)的修改必須提交Mutation
,Mutation
必須是同步執(zhí)行的麸澜。
- 當用戶點擊按鈕的時候挺尿,
count
值進行增加,先在store.js
中寫Mutation
函數(shù)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'hello vue'
},
mutations: {
// 增加函數(shù)炊邦,接收兩個參數(shù)
// 第一個state狀態(tài)
// 第二個是payload載荷编矾,payload是mutations的時候提交的額外參數(shù),可以是對象馁害,這里傳遞的是數(shù)字
increate (state, payload) {
state.count += payload
}
},
actions: {
},
modules: {
}
})
- 在
App.vue
中設(shè)置按鈕窄俏,并注冊事件
<template>
<div id="app">
<h1>Vuex - Demo</h1>
count: {{ $store.state.count }} <br/>
<h2>Mutation</h2>
<!-- 給按鈕注冊點擊事件,點擊的時候調(diào)用commit提交Mutation碘菜,第一個參數(shù)是調(diào)用的方法名凹蜈,第二個參數(shù)是payload,傳遞的數(shù)據(jù) -->
<button @click="$store.commit('increate', 2)">Mutation</button>
</div>
</template>
- 點擊按鈕的時候忍啸,
count
的值每次+2
- 下面進行寫法優(yōu)化仰坦,使用
map
方法將當前的mutation
映射到methods
中,其依舊會返回一個對象计雌,這個對象中存儲的是mutation
中映射的方法
<template>
<div id="app">
<h1>Vuex - Demo</h1>
count: {{ $store.state.count }} <br/>
<h2>Mutation</h2>
<!-- 這里直接寫方法悄晃,,第一個參數(shù)state不需要傳遞凿滤,后面?zhèn)鱬ayload參數(shù)為3 -->
<button @click="increate(3)">Mutation</button>
</div>
</template>
<script>
// 1. 引入vuex的mapMutations模塊
import { mapMutations } from 'vuex'
export default {
// 2. methods中調(diào)用mapMutations方法
methods: {
...mapMutations(['increate'])
}
}
</script>
Mutation的調(diào)試
運行到4
之后妈橄,這時看一下devtools
看一下時光旅行和歷史回滾,下面是初始狀態(tài)
點一下按鈕之后就增加了一個記錄翁脆,還顯示了改變之后的數(shù)據(jù)
如果數(shù)據(jù)不對眷蚓,可以進行調(diào)試。
時光旅行
然后多點幾下反番,進行時光旅行沙热。
點擊按鈕之后叉钥,狀態(tài)就變成了之前那個狀態(tài),這個功能也是為了方便調(diào)試
狀態(tài)回滾
這個圖標就是狀態(tài)回滾
點擊之后篙贸,代碼就回到了沒有執(zhí)行這一步的狀態(tài)
提交改變
下面那個按鈕的意思是將這次提交作為最后一次提交
點擊之后沼侣,base State
變成了那次的狀態(tài),其他的狀態(tài)以這個作為起始點
Actions的使用
如果有異步的修改歉秫,需要使用actions
,在actions
中可以執(zhí)行異步操作养铸,當異步操作結(jié)束后雁芙,如果需要更改狀態(tài),還需要提交Mutation
钞螟。
- 在
actions
中添加方法increateAsync
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'hello vue'
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {
// actions中的方法有兩個參數(shù):第一個參數(shù)是context上下文兔甘,這個對象中有state,commit鳞滨,getters等成員洞焙,第二個參數(shù)是payLoad
increateAsync (context, payLoad) {
setTimeout(() => {
context.commit('increate', payLoad)
}, 2000)
}
},
modules: {
}
})
- 在
App.vue
中使用dispatch
,actions
的方法都要用這個
<template>
<div id="app">
<h1>Vuex - Demo</h1>
count: {{ $store.state.count }} <br/>
<h2>Actions</h2>
<!--這里使用了dispatch-->
<button @click="$store.dispatch('increateAsync',5)">Action</button>
</div>
</template>
- 進行優(yōu)化拯啦,這個時候引入
mapActions
<template>
<div id="app">
<h1>Vuex - Demo</h1>
count: {{ $store.state.count }} <br/>
<h2>Actions</h2>
<button @click="increateAsync(5)">Action</button>
</div>
</template>
<script>
// 1. 引入vuex的mapActions模塊
import { mapActions } from 'vuex'
export default {
methods: {
// 這個是對Actions的方法的映射澡匪,把this.increateAsync映射到this.$store.dispatch
...mapActions(['increateAsync'])
}
}
</script>
Modules的使用
模塊可以讓我們把單一狀態(tài)樹拆分成多個模塊,每個模塊都可以擁有自己的state
褒链,mutation
唁情,action
,getter
甚至嵌套子模塊甫匹。
模塊定義
在store
文件夾中甸鸟,創(chuàng)建一個modules
文件夾,里面每一個js
文件就是一個模塊兵迅,下面是每一個模塊的定義格式
const state = {
products: [
{ id: 1, title: 'iPhone 11', price: 8000 },
{ id: 2, title: 'iPhone 12', price: 10000 }
]
}
const getters = {}
const mutations = {
setProducts (state, payload) {
state.products = payload
}
}
const actions = {}
export default {
namespaced: false,
state,
getters,
mutations,
actions
}
模塊注冊
- 先導(dǎo)入這個模塊
import products from './modules/products'
import cart from './modules/cart'
- 后來在
modules
選項中注冊抢韭,注冊之后這里會把模塊掛載到store
的state
中,這里可以通過store.state.products
訪問到products
模塊中的成員恍箭,還把的模塊中的mutation
成員記錄到了store
的內(nèi)部屬性_mutation
中刻恭,可以通過commit
直接提交mutation
。
export default new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {
products,
cart
}
})
模塊使用
- 在
App.vue
中使用季惯,state
就點出來吠各,mutation
還是用commit
方法
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<h2>Modules</h2>
<!--第一個products是products模塊,第二個products是模塊的state的products屬性-->
products: {{ $store.state.products.products }} <br/>
<button @click="store.commit('setProducts',[])">Mutation</button>
</div>
</template>
添加命名空間
因為每個模塊中的mutation
是可以重名的勉抓,所以推薦使用命名空間的用法贾漏,方便管理。
- 在開啟命名空間的時候藕筋,在模塊的導(dǎo)出部分添加
namespaced
const state = {}
const getters = {}
const mutations = {}
const actions = {}
export default {
// true就是開啟纵散,false或者不寫就是關(guān)閉
namespaced: false,
state,
getters,
mutations,
actions
}
- 使用的時候在
App.vue
中要設(shè)置state
是模塊中出來的,如果沒有命名空間,就是全局的state
的伍掀。
<template>
<div id="app">
<h1>Vuex - Demo</h1>
products: {{ products }} <br/>
<button @click="setProducts([])">Mutation</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
// 模塊中的state掰茶,第一個參數(shù)寫模塊名稱,第二個參數(shù)寫數(shù)組或者對象
...mapState('products', ['products'])
},
methods: {
// 模塊中的mutations蜜笤,第一個寫模塊名稱濒蒋,第二個寫數(shù)組或者對象
...mapMutations('products', ['setProducts'])
}
}
</script>
Vuex嚴格模式
所有的狀態(tài)變更必須提交mutation
,但是如果在組件中獲取到$store.state.msg
進行修改把兔,語法層面沒有問題沪伙,卻破壞了Vuex
的約定,且devTools
也無法跟蹤到狀態(tài)的修改县好,開啟嚴格模式之后围橡,如果在組件中直接修改state
,會報錯缕贡。
- 在
index.js
翁授,初始化Store
的時候開啟嚴格模式
export default new Vuex.Store({
strict: true,
state: {
...
},
...
}
- 在
App.vue
中使用直接賦值的語句
<template>
<div id="app">
<h1>Vuex - Demo</h1>
<h2>strict</h2>
<button @click="$store.state.msg = 'hello world~'">strict</button>
</div>
</template>
- 點擊按鈕內(nèi)容改變,但是控制臺會拋出錯誤
注意:不要在生產(chǎn)環(huán)境開啟嚴格模式晾咪,因為嚴格模式會深度檢測狀態(tài)樹收擦,會影響性能。在開發(fā)模式中開啟嚴格模式禀酱,在生產(chǎn)環(huán)境中關(guān)閉嚴格模式
export default new Vuex.Store({ strict: process.env.NODE_ENV !== 'production', state: { ... }
Vuex插件
-
Vuex
插件就是一個函數(shù)炬守,接收一個store
的參數(shù) - 在這個函數(shù)中可以注冊函數(shù)讓其在所有的
mutations
結(jié)束之后再執(zhí)行
插件的使用
- 插件應(yīng)該在創(chuàng)建
Store
之前去創(chuàng)建 -
subscribe
函數(shù)- 作用是去訂閱
store
中的mutation
- 他的回調(diào)函數(shù)會在每個
mutation
之后調(diào)用 -
subscribe
會接收兩個參數(shù),第一個是mutation
剂跟,還可以區(qū)分模塊的命名空間减途,第二個參數(shù)是state
,里面是存儲的狀態(tài)
- 作用是去訂閱
- 定義插件
// 這個函數(shù)接收store參數(shù)
const myPlugin = store => {
// 當store初始化后調(diào)用
store.subscribe((mutation, state) => {
// 每次 mutation 之后調(diào)用
// mutation 的格式為 { type, payload }
// type里面的格式是 "模塊名/state屬性"
// state 的格式為 { 模塊一, 模塊二 }
})
}
- 在
Store
中注冊插件
const store = new Vuex.Store({
//...
plugins: [myPlugin]
})