在開發中,我們會用應用程式處理很多的資料,這些資料需要儲存在我們應用程式的某一個位置,對于這些資料的管理我們就稱之為狀态管理。
Vuex是一個專門為Vue.js應用程式開發的狀态管理模式。它采用集中式存儲管理應用的所有元件的狀态,解決多元件資料通信問題。
一、Vuex核心
Vuex 可以幫助我們管理共享狀态,并附帶了更多的概念和架構。這需要對短期和長期效益進行權衡。
如果你不打算開發大型單頁應用,使用 Vuex 可能是繁瑣備援的。确實是如此——如果你的應用夠簡單,您最好不要使用 Vuex。一個簡單的 store 模式 (opens new window)就足夠你所需了。但是,如果你需要建構一個中大型單頁應用,你很可能會考慮如何更好地在元件外部管理狀态,Vuex 将會成為自然而然的選擇。
VueX是采用集中式管理元件依賴的共享資料的一個工具,可以解決不同元件資料共享問題。
1、State:
包含了store中存儲的各個資料。
用法:
const store = new Vuex.Store({
state:{
count:0
}
})
元件通路State中資料的三種方式:
- this.$store.state.count
<template>
<div> state的資料:{{ $store.state.count }}</div>
<template>
<script>
// 安裝完 vuex 會在元件中注入一個 $store 的變量
created() {
console.log(this.$store.state.count);
}
</script>
- 使用計算屬性computed
<template>
<div> state的資料:{{ count }}</div>
<template>
<script>
// 把state中資料,定義在元件内的計算屬性中
computed: {
count () {
return this.$store.state.count
}
}
</script>
每當 $store.state.count 變化的時候, 都會重新求取計算屬性,并且觸發更新相關聯的 DOM。
然而,這種模式導緻元件依賴全局狀态單例。在子產品化的建構系統中,在每個需要使用 state 的元件中需要頻繁地導入,并且在測試元件時需要模拟狀态。
- 借助mapState輔助函數映射
<template>
<div> state的資料:{{ count }}</div>
<template>
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['count'])
}
}
2、Getter
除了state之外,有時我們還需要從state中派生出一些狀态,這些狀态是依賴state的,此時會用到getters。類似于Vue中的計算屬性,根據getter或者state計算傳回值。
例如:對清單進行過濾并計數
const store = new Vuex.Store({
state: {
count: [1,2,3,4,5,6,7,8,9,10]
},
getters: {
// getters函數的第一個參數是 state
// 必須要有傳回值
getCount: state => state.list.filter(item => item > 5)
}
})
元件通路getters中資料的兩種方式:
- this.$store.getters.getCount
<template>
<div>
<div> state的資料:{{ getCount }}</div>
</div>
<template>
<script>
// 把state中資料,定義在元件内的計算屬性中
computed: {
getCount () {
return this.$store.getters.getCount
}
}
</script>
- 借助mapGetters輔助函數映射
import {mapGetters} from 'vuex'
export default{
computed:{
...mapGetters(['getCount'])
}
}
3、Mutation
Vuex裡store狀态更新的唯一方法,且必須是同步函數。
用法:
const store = new Vuex.Store({
state:{
count:0
},
mutations:{
// 接受 state作為第一個參數
mutationCount(state){
state.count ++ ;
}
// 向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload)
addCount (state, payload) {
state.count += payload.data
}
}
})
元件通路mutations中資料的兩種方式:
- 使用store.commit調用mutation。
methods:{
changeCount(){
this.$store.commit('mutationCount')
}
}
- 借助mapMutations輔助函數映射
import {mapMutations} from 'vuex'
export default{
methods:{
...mapMutations(['mutationCount'])
}
}
在 mutation 中混合異步調用會導緻你的程式很難調試。
mutations: {
someMutation (state) {
setTimeout(() => {
state.count++
},1000)
}
}
現在想象,我們正在debug一個app并且觀察 devtool中的mutation日志。每一條mutation被記錄,devtools都需要捕捉到前一狀态和後一狀态的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:因為當 mutation 觸發的時候,回調函數還沒有被調用,devtools 不知道什麼時候回調函數實際上被調用——實質上任何在回調函數中進行的狀态的改變都是不可追蹤的。
是以,在 Vuex 中mutation 都是同步函數。
4、Action
用于送出mutation,而不是直接變更狀态。可異步。
Action類似于Mutation,不同的在于:
(1)Action送出的是Mutation,而不是直接變更狀态
(2)Action可以包含任何異步操作
用法:
const store = new Vuex.Store({
state:{
count:0
},
mutations:{
mutationCount(state,payload){
state.count = payload;
}
},
actions:{
actionCount(context,payload){
setTimeout(()=>{
context.commit('mutationCount',payload.count)
},1000)
}
}
})
元件通路actions中資料的兩種方式:
- 使用store.dispatch來分發action。
methods:{
changeCountAsync(){
this.$store.dispatch('actionCount',res)
}
}
- 借助mapActions輔助函數映射
import {mapActions} from 'vuex'
export default{
methods:{
...mapActions(['actionCount'])
}
}
最後,如果我們利用 async / await ,我們可以組合 action:
// 假設 getData() 和 getOtherData() 傳回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一個 store.dispatch 在不同子產品中可以觸發多個 action 函數。在這種情況下,隻有當所有觸發函數完成後,傳回的 Promise 才會執行。
5、 Module
允許将單一的Store拆分為多個store。
由于使用單一狀态樹,應用的所有狀态會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。
為了解決以上問題,Vuex 允許我們将 store 分割成子產品(module)。每個子產品擁有自己的 state、mutation、action、getter、甚至是嵌套子子產品——從上至下進行同樣方式的分割:
每個子產品都擁有獨立的state、getters、mutations、actions
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 的狀态
store.state.b // -> moduleB 的狀态
子產品的局部狀态
對于子產品内部的 mutation 和 getter,接收的第一個參數是子產品的局部狀态對象。
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 這裡的 `state` 對象是子產品的局部狀态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
對于子產品内部的 getter,根節點狀态會作為第三個參數暴露出來:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空間
預設情況下,子產品内部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個子產品能夠對同一 mutation 或 action 作出響應。
如果希望你的子產品具有更高的封裝度和複用性,你可以通過添加 namespaced: true 的方式使其成為帶命名空間的子產品。當子產品被注冊後,它的所有 getter、action 及 mutation 都會自動根據子產品注冊的路徑調整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 子產品内容(module assets)
state: () => ({ ... }), // 子產品内的狀态已經是嵌套的了,使用 `namespaced` 屬性不會對其産生影響
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套子產品
modules: {
// 繼承父子產品的命名空間
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 進一步嵌套命名空間
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
啟用了命名空間的 getter 和 action 會收到局部化的 getter,dispatch 和 commit。換言之,你在使用子產品内容(module assets)時不需要在同一子產品内額外添加空間名字首。更改 namespaced 屬性後不需要修改子產品内的代碼。
二、Vue3中使用Vuex
1. 安裝Vuex。
npm install vuex --save
2. 配置Vuex。
在src/store/index.js中配置
import {createStore} from 'vuex';
export default createStore({
})
3. 在main.js中引入Vuex。
import {createApp} from 'vue';
import App from './App.vue';
import {store} from './store';
createApp(App).use(store).mount('#app);
4. 在元件中讀取資料。
三、Vuex資料響應式原理
Vuex完美的結合了Vue的響應式資料。那麼它的原理是什麼呢?
每一個Vue的插件,都需要一個公開的install方法,Vuex也不例外。
// src/store.js
export function install (_Vue) {
if (Vue && _Vue === Vue) {
return
}
Vue = _Vue
applyMixin(Vue)//在所有元件的beforeCreate注入了設定this.$store這樣的一個對象,
//在任意元件中執行this.$store都能找到store對象
}
Vuex.Store的構造函數中,有一堆初始化以及resetStoreVM(this,state)整個Vuex的關鍵。
// src/store.js
function resetStoreVM (store, state, hot) {
// 省略無關代碼
Vue.config.silent = true
//本質:将我們傳入的state作為一個隐藏的vue元件的data
//也就是說,我們的commit操作,本質上其實是修改這個元件的data
store._vm = new Vue({
data: {
$state: state
},
computed
})
}
//this.$store._vm.$data.$state===this.$store.state
Vuex的state狀态是響應式,是借助Vue的data是響應式,将state存入Vue執行個體元件的data中;Vuex的getters則是借助Vue的計算屬性computed實作資料實時監聽。
computed計算屬性監聽data資料變更主要經曆以下幾個過程:
四、Vuex适用場景
1. Vuex一般用于中大型web單頁應用中對應用的狀态進行管理。
2. Vuex解決非父子元件之間的通信問題。
3. Vuex做為資料存儲中心。如購物車、登陸狀态。
五、總結
Vuex是針對Vuejs的狀态管理工具。Vuex完美的結合了Vue的響應式資料。
Vue可以直接觸發methods中的方法,Vuex不行。為了處理異步,當你觸發一個點選事件時,會通過dispatch來通路actions中的方法,actions中的commit會觸發mutations中的方法進而修改state的值,通過getters來把資料反映到視圖。
Vuex通過Vue.use(Vuex),進而調用install方法,通過applyMixin(Vue)在任意元件執行this.$store都能通路到store對象,實作将store挂載注入到元件中。
Vuex的state狀态是響應式的,是借助Vue的data是響應式,将state存入Vue執行個體元件的data中。
Vuex的getters則是借助于Vue的計算屬性computed實作資料的實時監聽。