Vuex狀態(tài)管理
Vuex是專門為Vue應用程序提供的狀態(tài)管理模式,每個Vuex應用的核心是store
(倉庫)湿镀,即裝載應用程序state
(狀態(tài))的容器,每個應用通常只擁有一個store
實例。
Vuex的state
是響應式的赋咽,即store
中的state
發(fā)生變化時,相應組件也會進行更新吨娜,修改store
當中state
的唯一途徑是提交mutations
脓匿。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
store.commit("increment") // 通過store.state來獲取狀態(tài)對象
console.log(store.state.count) // 通過store.commit()改變狀態(tài)
State
從store
當中獲取state
的最簡單辦法是在計算屬性中返回指定的state
,每當state
發(fā)生改變的時候都會重新執(zhí)行計算屬性宦赠,并且更新關聯(lián)的DOM陪毡。
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
Vuex提供store
選項,將state
從根組件注入到每個子組件中勾扭,從而避免頻繁import store
毡琉。
// 父組件中注冊store屬性
const app = new Vue({
el: "#app",
store: store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>`
})
// 子組件,store會注入到子組件妙色,子組件可通過this.$store進行訪問
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
Vuex提供mapState()
輔助函數(shù)桅滋,避免使用多個state
的場景下,多次去聲明計算屬性身辨。
// 在單獨構建的版本中輔助函數(shù)為 Vuex.mapState
import { mapState } from "vuex"
export default {
computed: mapState({
count: state => state.count,
// 傳遞字符串參數(shù)"count"等同于`state => state.count`
countAlias: "count",
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
// 當計算屬性名稱與state子節(jié)點名稱相同時丐谋,可以向mapState傳遞一個字符串數(shù)組
computed: mapState([
"count" // 映射this.count到store.state.count
])
mapState()
函數(shù)返回一個包含有state
相關計算屬性的對象,這里可以通過ES6的對象展開運算符...
將該對象與Vue組件本身的computed
屬性進行合并煌珊。
computed: {
localComputed () {},
...mapState({})
}
Vuex允許在store
中定義getters
(可視為store的計算屬性)笋鄙,getters
的返回值會根據(jù)其依賴被緩存,只有當依賴值發(fā)生了改變才會被重新計算怪瓶。該方法接收state
作為第1個參數(shù)萧落,其它getters
作為第2個參數(shù)践美。可以直接在store
上調用getters
來獲取指定的計算值找岖。
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: "...", done: true },
{ id: 2, text: "...", done: false }
]
},
getters: {
doneTodos: (state, getters) => {
return state.todos.filter(todo => todo.done)
}
}
})
// 獲取doneTodos = [{ id: 1, text: "...", done: true }]
store.getters.doneTodos
這樣就可以方便的根據(jù)store
中現(xiàn)有的state
派生出新的state
陨倡,從而避免在多個組件中復用時造成代碼冗余。
computed: {
doneTodosCount () {
// 現(xiàn)在可以方便的在Vue組件使用store中定義的doneTodos
return this.$store.getters.doneTodos
}
}
Vuex提供的mapGetters()
輔助函數(shù)將store
中的getters
映射到局部計算屬性许布。
import { mapGetters } from "vuex"
export default {
computed: {
// 使用對象展開運算符將getters混入computed計算屬性
...mapGetters([
"doneTodosCount",
// 映射store.getters.doneTodosCount到別名this.doneCount
doneCount: "doneTodosCount"
])
}
}
Mutations
修改store中的state的唯一方法是提交mutation([mju?"te??(?)n] n.變化)兴革,mutations類似于自定義事件,擁有一個字符串事件類型和一個回調函數(shù)(接收state作為參數(shù)蜜唾,是對state進行修改的位置)杂曲。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 觸發(fā)類型為increment的mutation時被調用
increment (state) {
state.count++ // 變更狀態(tài)
}
}
})
// 觸發(fā)mutation
store.commit("increment")
可以通過store的commit()
方法觸發(fā)指定的mutations,也可以通過store.commit()
向mutation傳遞參數(shù)袁余。
// commit()
store.commit({
type: "increment",
amount: 10
})
// store
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
mutation事件類型建議使用常量擎勘,并且將這些常量放置在單獨文件,便于管理和防止重復颖榜。
// mutation-types.js
export const SOME_MUTATION = "SOME_MUTATION"
// store.js
import Vuex from "vuex"
import { SOME_MUTATION } from "./mutation-types"
const store = new Vuex.Store({
state: { ... },
mutations: {
// 可以通過ES6的計算屬性命名特性去使用常量作為函數(shù)名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
mutation()
必須是同步函數(shù)棚饵,因為devtool無法追蹤回調函數(shù)中對state
進行的異步修改。
Vue組件可以使用this.$store.commit("xxx")
提交mutation掩完,或者使用mapMutations()
將Vue組件中的methods
映射為store.commit
調用(需要在根節(jié)點注入store
)噪漾。
import { mapMutations } from "vuex"
export default {
methods: {
...mapMutations([
"increment" // 映射this.increment()為this.$store.commit("increment")
]),
...mapMutations({
add: "increment" // 映射this.add()為this.$store.commit("increment")
})
}
}
Actions
Action用來提交mutation,且Action中可以包含異步操作且蓬。Action函數(shù)接受一個與store實例具有相同方法和屬性的context
對象欣硼,因此可以通過調用context.commit
提交一個mutation,或者通過context.state
和context.getters
來獲取state恶阴、getters分别。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit("increment")
}
}
})
生產環(huán)境下,可以通過ES6的解構參數(shù)來簡化代碼存淫。
actions: {
// 直接向action傳遞commit方法
increment ({ commit }) {
commit("increment")
}
}
Action通過store.dispatch()
方法進行分發(fā),mutation當中只能進行同步操作沼填,而action內部可以進行異步的操作桅咆。下面是一個購物車的例子,代碼中分發(fā)了多個mutations坞笙,并進行了異步API操作岩饼。
actions: {
checkout ({ commit, state }, products) {
const savedCartItems = [...state.cart.added] // 把當前購物車的物品備份起來
commit(types.CHECKOUT_REQUEST) // 發(fā)出結賬請求,然后清空購物車
// 購物Promise分別接收成功和失敗的回調
shop.buyProducts(
products,
() => commit(types.CHECKOUT_SUCCESS), // 成功操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems) // 失敗操作
)
}
}
組件中可以使用this.$store.dispatch("xxx")
分發(fā)action薛夜,或者使用mapActions()
將組件的methods
映射為store.dispatch
(需要在根節(jié)點注入store
)籍茧。
import { mapActions } from "vuex"
export default {
methods: {
...mapActions([
"increment" // 映射this.increment()為this.$store.dispatch("increment")
]),
...mapActions({
add: "increment" // 映射this.add()為this.$store.dispatch("increment")
})
}
}
store.dispatch
可以處理action
回調函數(shù)當中返回的Promise
,并且store.dispatch
本身仍然返回一個Promise
梯澜。
actions: {
// 定義一個返回Promise對象的actionA
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit("someMutation") // 觸發(fā)mutation
resolve()
}, 1000)
})
},
// 也可以在actionB中分發(fā)actionA
actionB ({ dispatch, commit }) {
return dispatch("actionA").then(() => {
commit("someOtherMutation") // 觸發(fā)另外一個mutation
})
}
}
// 現(xiàn)在可以分發(fā)actionA
store.dispatch("actionA").then(() => {
... ... ...
})
可以體驗通過ES7的異步處理特性async
/await
來組合action寞冯。
actions: {
async actionA ({ commit }) {
commit("gotData", await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch("actionA") //等待actionA完成
commit("gotOtherData", await getOtherData())
}
}
Module
整個應用使用單一狀態(tài)樹的情況下,所有state都會集中到一個store對象,因此store可能變得非常臃腫吮龄。因此俭茧,Vuex允許將store切割成模塊(module),每個模塊擁有自己的state
漓帚、mutation
母债、action
、getter
尝抖、甚至是嵌套的子模塊毡们。
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)
module內部的mutations()
和getters()
接收的第1個參數(shù)是模塊的局部狀態(tài)對象。
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
state.count++ // 這里的state是模塊的局部狀態(tài)
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
模塊內部action當中昧辽,可以通過context.state
獲取局部狀態(tài)衙熔,以及context.rootState
獲取全局狀態(tài)。
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit("increment")
}
}
}
}
模塊內部的getters()
方法奴迅,可以通過其第3個參數(shù)接收到全局狀態(tài)青责。
const moduleA = {
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
嚴格模式
嚴格模式下,如果state變化不是由mutation()
函數(shù)引起取具,將會拋出錯誤脖隶。只需要在創(chuàng)建store
的時候傳入strict: true
即可開啟嚴格模式。
const store = new Vuex.Store({
strict: true
})
不要在生產環(huán)境下啟用嚴格模式暇检,因為它會深度檢測不合法的state變化产阱,從而造成不必要的性能損失,我們可以通過在構建工具中增加如下判斷避免這種情況块仆。
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== "production"
})
嚴格模式下构蹬,在屬于Vuex的state上使用v-model指令會拋出錯誤,此時需要手動綁定value并監(jiān)聽input悔据、change事件庄敛,并在事件回調中手動提交action。另外一種實現(xiàn)方式是直接重寫計算屬性的get和set方法科汗。
總結
- 應用層級的狀態(tài)應該集中到單個store對象中藻烤。
- 提交
mutation
是更改狀態(tài)的唯一方法,并且這個過程是同步的头滔。 -
異步邏輯都應該封裝到
action
里面怖亭。
Webpack Vue Loader
vue-loader是由Vue開源社區(qū)提供的Webpack加載器,用來將.vue
后綴的單文件組件轉換為JavaScript模塊坤检,每個.vue
單文件組件可以包括以下部分:
- 一個
<template>
兴猩。 - 一個
<script>
。 - 多個
<style>
早歇。
<template>只能有1個</template>
<script>只能有1個</script>
<style>可以有多個</style>
<style>可以有多個</style>
<style>可以有多個</style>
CSS作用域
向.vue
單文件組件的<style>
標簽上添加scoped
屬性倾芝,可以讓該<style>
標簽中的樣式只作用于當前組件讨勤。使用scoped時,樣式選擇器盡量使用class或者id蛀醉,以提升頁面渲染性能悬襟。
<!-- 單文件組件定義 -->
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">Hank</div>
</template>
<!-- 轉換結果 -->
<style>
.example[data-v-f3f3eg9] {
color: blue;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>Hank</div>
</template>
可以在一個組件中同時使用帶
scoped
屬性和不帶該屬性的<style/>
,分別用來定義組件私有樣式和全局樣式拯刁。
CSS模塊化
在單文件組件.vue
的<style>
標簽上添加module
屬性即可打開CSS模塊化特性脊岳。CSS Modules用于模塊化組合CSS,vue-loader已經集成了CSS模塊化特性垛玻。
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
CSS模塊會向Vue組件中注入名為$style
計算屬性割捅,從而實現(xiàn)在組件的<template/>
中使用動態(tài)的class
屬性進行綁定。
<template>
<p :class="$style.red">
This should be red
</p>
</template>
動畫
Vue在插入帚桩、更新亿驾、移除DOM的時候,提供了如下幾種方式去展現(xiàn)進入(entering)和離開(leaving)的過渡效果账嚎。
- 在CSS過渡和動畫中應用class莫瞬。
- 鉤子過渡函數(shù)中直接操作DOM。
- 使用CSS郭蕉、JavaScript動畫庫疼邀,如Animate.css、Velocity.js召锈。
transition組件
Vue提供了內置組件<transition/>
來為HTML元素旁振、Vue組件添加過渡動畫效果,可以在條件展示(使用v-if
或v-show
)涨岁、動態(tài)組件拐袜、展示組件根節(jié)點的情況下進行渲染。<transition/>
主要用來處理單個節(jié)點梢薪,或者同時渲染多個節(jié)點當中的一個蹬铺。
自動切換的class類名
在組件或HTML進入(entering)和離開(leaving)的過渡效果當中,Vue將會自動切換并應用下圖中的六種class類名秉撇。
可以使用<transition/>
的name
屬性來自動生成過渡class類名甜攀,例如下面例子中的name: "fade"
將自動拓展為.fade-enter
,.fade-enter-active
等畜疾,name
屬性缺省的情況下默認類名為v
。
<div id="demo">
<button v-on:click="show = !show"> Toggle </button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
<script>
new Vue({
el: "#demo",
data: {
show: true
}
})
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to {
opacity: 0
}
</style>
自定義CSS類名
結合Animate.css
使用時印衔,可以在<transition/>
當中通過以下屬性自定義class類名啡捶。
<transition
enter-class = "animated"
enter-active-class = "animated"
enter-to-class = "animated"
leave-class = "animated"
leave-active-class = "animated"
leave-to-class = "animated">
</transition>
自定義JavaScript鉤子
結合Velocity.js
使用時,通過v-on在屬性中設置鉤子函數(shù)奸焙。
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled">
</transition>
<script>
// ...
methods: {
beforeEnter: function (el) {},
enter: function (el, done) { done() },
afterEnter: function (el) {},
enterCancelled: function (el) {},
beforeLeave: function (el) {},
leave: function (el, done) { done() },
afterLeave: function (el) {},
leaveCancelled: function (el) {} // 僅用于v-show
}
</script>
顯式設置過渡持續(xù)時間
可以使用<transition>
上的duration屬性
設置一個以毫秒為單位的顯式過渡持續(xù)時間瞎暑。
<transition :duration="1000"> Hank </transition>
<!-- 可以分別定制進入彤敛、移出的持續(xù)時間 -->
<transition :duration="{ enter: 500, leave: 800 }"> Hank </transition>
組件首次渲染時的過渡
通過<transition>
上的appear屬性
設置組件節(jié)點首次被渲染時的過渡動畫。
<!-- 自定義CSS類名 -->
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class"
appear-active-class="custom-appear-active-class">
</transition>
<!-- 自定義JavaScript鉤子 -->
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook">
</transition>
HTML元素的過渡效果
Vue組件的key屬性
key屬性主要用在Vue虛擬DOM算法中去區(qū)分新舊VNodes了赌,不顯式使用key
的時候墨榄,Vue會使用性能最優(yōu)的自動比較算法。顯式的使用key
勿她,則會基于key
的變化重新排列元素順序袄秩,并移除不存在key
的元素。具有相同父元素的子元素必須有獨特的key
逢并,因為重復的key
會造成渲染錯誤之剧。
<ul>
<!-- 最常見的用法是在使用v-for的時候 -->
<li v-for="item in items" :key="item.id">...</li>
</ul>
元素的的交替過渡
可以通過Vue提供的v-if
和v-else
屬性來實現(xiàn)多組件的交替過渡,最常見的過渡效果是一個列表以及描述列表為空時的消息砍聊。
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
Vue中具有相同名稱的元素切換時背稼,需要通過關鍵字key
作為標記進行區(qū)分,否則Vue出于效率的考慮只會替換相同標簽內的內容玻蝌,因此為<transition>
組件中的同名元素設置key
是一個最佳實踐蟹肘。
<transition>
<button v-if="isEditing" key="save"> Save </button>
<button v-else key="edit"> Edit </button>
</transition>
一些場景中,可以通過給相同HTML元素的key
屬性設置不同的狀態(tài)來代替冗長的v-if
和v-else
俯树。
<!-- 通過v-if和v-else來實現(xiàn) -->
<transition>
<button v-if="isEditing" key="save"> Save </button>
<button v-else key="edit"> Edit </button>
</transition>
<!-- 設置動態(tài)的key屬性來實現(xiàn) -->
<transition>
<button v-bind:key="isEditing"> {{isEditing ? "Save" : "Edit"}} </button>
</transition>
而對于使用了多個v-if
的多元素過渡帘腹,也可以通過動態(tài)的key
屬性進行大幅度的簡化。
<!-- 多個v-if實現(xiàn)的多元素過渡 -->
<transition>
<button v-if="docState === "saved"" key="saved"> Edit </button>
<button v-if="docState === "edited"" key="edited"> Save </button>
<button v-if="docState === "editing"" key="editing"> Cancel </button>
</transition>
<!-- 通過動態(tài)key屬性可以大幅簡化模板代碼 -->
<transition>
<button v-bind:key="docState"> {{ buttonMessage }} </button>
</transition>
<script>
...
computed: {
buttonMessage: function () {
switch (this.docState) {
case "saved": return "Edit"
case "edited": return "Save"
case "editing": return "Cancel"
}
}
}
</script>
Vue組件的過渡效果
多個Vue組件之間的過渡不需要使用key
屬性聘萨,只需要使用動態(tài)組件即可竹椒。
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
<script>
new Vue({
el: "#transition-components-demo",
data: {
view: "v-a"
},
components: {
"v-a": {
template: "<div>Component A</div>"
},
"v-b": {
template: "<div>Component B</div>"
}
}
})
<script>
<style>
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to {
opacity: 0;
}
<style>
選擇HTML元素或Vue組件的過渡模式
<transition>
的默認進入(enter)和離開(leave)行為同時發(fā)生,所以當多個需要切換顯示的HTML元素或Vue組件處于相同位置的時候米辐,這種同時生效的進入和離開過渡不能滿足所有需求胸完,Vue可以通過<transition-gruop>
組件的mode
屬性來選擇如下過渡模式。
-
in-out
:新元素先進行過渡翘贮,完成之后當前顯示的元素再過渡離開赊窥。 -
out-in
:當前顯示的元素先進行過渡,完成之后新元素再過渡進入狸页。
<transition name="fade" mode="out-in">
<button v-if="docState === "saved"" key="saved"> Edit </button>
<button v-if="docState === "edited"" key="edited"> Save </button>
<button v-if="docState === "editing"" key="editing"> Cancel </button>
</transition>
transition-group組件
<transition-group>
用來設置多個HTML元素或Vue組件的過渡效果锨能,不同于<transition>
,該組件默認會被渲染為一個真實的<span>
元素芍耘,但是開發(fā)人員也可以通過<transition-group>
組件的tag
屬性更換為其它合法的HTML元素址遇。<transition-group>
組件內部的元素必須要提供唯一的key
屬性值。
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
<script>
new Vue({
el: "#list-demo",
data: {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
</script>
<style>
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>
<transition-group>
實現(xiàn)的列表過渡效果在添加斋竞、移除某個HTML元素時倔约,相臨的其它HTML元素會瞬間移動至新位置,這個過程并非平滑的過渡坝初。為解決這個問題浸剩,<transition-group>
提供v-move特性去覆蓋移動過渡期間所使用的CSS類名钾军。開啟該特性,即可以通過name
屬性手動設置(下面例子中的name="flip-list"
與.flip-list-move
)绢要,也可以直接使用move-class
屬性吏恭。
<div id="flip-list-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" v-bind:key="item">
{{ item }}
</li>
</transition-group>
</div>
<script>
new Vue({
el: "#flip-list-demo",
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
</script>
<style>
.flip-list-move {
transition: transform 1s;
}
<style>
可以通過響應式的綁定<transition>
和<transition-gruop>
上的name屬性,從而能夠根據(jù)組件自身的狀態(tài)實現(xiàn)具有動態(tài)性的過渡效果重罪。
<transition v-bind:name="transitionName"></transition>