Vuex 狀態(tài)管理模式

Vuex 狀態(tài)管理模式

在使用vue開發(fā)過程中到忽,經(jīng)常會遇到一個狀態(tài)在多個組件之間使用,這時候就需要用vuex來狀態(tài)管理模式來集中管理解決跨組件通信問題,跨組件通信一般是指非父子組件間 的通信。

一顿天、核心概念
在這里插入圖片描述

  • store:vuex使用一個倉庫store對象管理應(yīng)用的狀態(tài)和操作過程,一個 store 包括 state, getter, mutation, action 四個屬性蔑担。vuex里的數(shù)據(jù)都是響應(yīng)式的牌废,任何組件使用同意store的數(shù)據(jù)時,只要store的數(shù)據(jù)變化啤握,對應(yīng)的組件也會實時更新鸟缕。
  • state:狀態(tài),vuex的數(shù)據(jù)源。
  • getter:getter就是對狀態(tài)進行處理的提取出來的公共部分懂从,對state進行過濾輸出授段。
  • mutation:提交mutation是更改vuex 的store中的狀態(tài)的唯一方法,并且只能是同步操作番甩。
  • action:對state的異步操作侵贵,并通過在action提交mutation變更狀態(tài)。
  • module:當(dāng)store對象過于龐大時缘薛,可以分成多個module窍育,每個module也會有state, getter, mutation, action 四個屬性。

二掩宜、安裝

在使用vue-cli腳手架生成項目使用選擇vuex蔫骂,也可以在使用以下命令安裝

npm install vuex --save

結(jié)構(gòu)如下:

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

main.js

通過在main.js注冊 store,該 store 實例會注入到根組件下的所有子組件中牺汤,且子組件能通過 this.$store 訪問到。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

三浩嫌、State

state保存狀態(tài)檐迟,先在state中定義一個狀態(tài)count

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {},
  actions: {},
  modules: {}
}

現(xiàn)在有一個集中管理的狀態(tài)count,其他組件可以通過計算屬性來返回此狀態(tài)码耐,由于在main.js注冊過store追迟,所以可以使用this.$store就可以獲取到state。

<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>

<script>
  export default {
    computed: {
      count() {
        return this.$store.state.count;
      }
    }
 }
</script>

mapState 輔助函數(shù)

當(dāng)一個組件需要多個狀態(tài)時骚腥,為這些狀態(tài)都做計算屬性會很冗余麻煩敦间,可以使用 mapState 輔助函數(shù)來幫助生成計算屬性。

<template>
  <div>
    <p>{{count}}</p>
    <p>{{calculation}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    data() {
      return {
        msg: "計數(shù):"
      }
    },
    computed: mapState({

      // 簡寫
      // count: state => state.count,

      count(state) {
        return state.count
      },


      // 使用組件內(nèi)狀態(tài)+store狀態(tài)
      calculation(state) {
        return this.msg + state.count
      }
    })
  }
</script>

對象展開運算符

...對象展開運算符束铭,...mapState語法糖簡化了寫法

<template>
  <div>
    <p>{{count}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    computed: {
      ...mapState(['count'])
    }
  }
</script>

四廓块、Getter

getter就是對狀態(tài)進行帥選過濾操作,處理過后返回給組件使用契沫。

先定義一組list带猴,然后再對list篩選。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    list: [{
        name: "魯班七號",
        game: "王者榮耀"
      },
      {
        name: "亞索",
        game: "英雄聯(lián)盟"
      }, 
      {
        name: "德瑪西亞之力",
        game: "英雄聯(lián)盟"
      }, {
        name: "亞瑟",
        game: "王者榮耀"
      },
      {
        name: "阿古朵",
        game: "王者榮耀"
      },
      {
        name: "努努",
        game: "英雄聯(lián)盟"
      }
    ]
  },
  getters: {
    lol: state => {
      return state.list.filter(p => p.game=="英雄聯(lián)盟");
    }
  },
  mutations: {},
  actions: {},
  modules: {}
})

getter會暴露出store.getters 對象懈万,可以以屬性的方式訪問

<template>
  <div>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
  export default {
    computed: {
      list() {
        return this.$store.getters.lol;
      },
    }
  }
</script>

mapGetters 輔助函數(shù)

mapGetters輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計算屬性

<template>
  <div>
    <ul>
      <li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
  import {
    mapGetters
  } from 'vuex'
  export default {
    computed: {
     ...mapGetters({
         list: 'lol'
     })
    }
  }
</script>

五拴清、Mutation

如果我們想修改store里的state狀態(tài)值時,我們不可以直接在組件內(nèi)去修改会通,而是通過提交mutation來進行修改口予,mutation類似于事件。我們來實現(xiàn)一個加減計數(shù)涕侈。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
   count:0
  },
  getters: {},
  mutations: {
    // 增加
    increase(state){
      state.count++
    },
    // 減少
    decrease(state){
      state.count--
    }
  },
  actions: {},
  modules: {}
})

在組件內(nèi)沪停,通過this.$store.commit方法來執(zhí)行mutation,

<template>
  <div>
    <input type="button" value="+" @click="increase" />
    <input type="button" value="-" @click="decrease" />
    <p>計數(shù):{{count}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    methods: {
      increase() {
        this.$store.commit('increase');
      },
      decrease() {
        this.$store.commit('decrease');
      }
    },
    computed: {
      ...mapState(['count'])
    }
  }

</script>

Payload 提交載荷

在提價mutation時可以傳入額外的參數(shù)驾凶,即荷載(payload)

例如我想count每次改變自定義值

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {},
  mutations: {
    // 增加
    increase(state, payload) {
      state.count += payload
    },
    // 減少
    decrease(state, payload) {
      state.count -= payload
    }
  },
  actions: {},
  modules: {}
})
<template>
  <div>
    <input type="button" value="+" @click="increase" />
    <input type="button" value="-" @click="decrease" />
    <p>計數(shù):{{count}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    methods: {
      increase() {
        this.$store.commit('increase',10);  //每次加10
      },
      decrease() {
        this.$store.commit('decrease',5); //每次減5
      }
    },
    computed: {
      ...mapState(['count'])
    }
  }
</script>

官網(wǎng)建議大多數(shù)情況下載荷應(yīng)該是一個對象牙甫,這樣可以包含多個字段并且記錄的 mutation 會更易讀:

  mutations: {
    // 增加
    increase(state, payload) {
      state.count += payload.customcount
    },
    // 減少
    decrease(state, payload) {
      state.count -= payload.customcount
    }
  },
    methods: {
      increase() {
        this.$store.commit('increase', {
          customcount: 10
        }); //每次加10
      },
      decrease() {
        this.$store.commit('decrease', {
          customcount: 5
        }); //每次減5
      }
    },

也可以寫成直接包含type屬性掷酗,也就是mutations里的事件(例如:increase、decrease)

    methods: {
      increase() {
        this.$store.commit({
          type:"increase",
          customcount: 10
        }); //每次加10
      },
      decrease() {
        this.$store.commit({
          type:"decrease",
          customcount: 5
        }); //每次減5
      }
    },

Mutation 需遵守 Vue 的響應(yīng)規(guī)則

既然 Vuex 的 store 中的狀態(tài)是響應(yīng)式的窟哺,那么當(dāng)我們變更狀態(tài)時泻轰,監(jiān)視狀態(tài)的 Vue 組件也會自動更新。這也意味著 Vuex 中的 mutation 也需要與使用 Vue 一樣遵守一些注意事項:

  1. 最好提前在你的 store 中初始化好所有所需屬性且轨。
  2. 當(dāng)需要在對象上添加新屬性時浮声,你應(yīng)該
  • 使用 Vue.set(obj, 'newProp', 123), 或者

  • 以新對象替換老對象。例如旋奢,利用對象展開運算符可以這樣寫:

    state.obj = { ...state.obj, newProp: 123 }
    

例如

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    people:[]
  },
  getters: {},
  mutations: {
    addNewProp(state,payload){
      
      //新增對象屬性
      // Vue.set(state.people,"name","Tom");

      //用新對象替換老對象
      state.people= {...state.people, name: 'Jerry'} 
    }
  },
  actions: {},
  modules: {}
})
<template>
  <div>
    <input type="button" value="新增屬性" @click="addNewProp" />
    <p>{{name}}</p>
  </div>
</template>

<script>
  import {
    mapState
  } from 'vuex'
  export default {
    methods: {
      addNewProp() {
        this.$store.commit('addNewProp');
      }
    },
    computed: {
      name() {
        return this.$store.state.people.name;
      },
    }}
</script>

Mutation必須是同步函數(shù)

官網(wǎng)給的例子泳挥,當(dāng)mutation觸發(fā)的時候,回調(diào)函數(shù)還沒有被調(diào)用至朗,回調(diào)函數(shù)中進行的狀態(tài)的改變都是不可追蹤的屉符。

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

mapMutations 輔助函數(shù)

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射為 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射為 `this.$store.commit('increment')`
    })
  }
}

六、Action

action類似于mutation锹引,不同在于

  • Action可以提交mutation矗钟,而不是直接變更狀態(tài)
  • Action可以包含任意異步操作
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    getters: {},
    mutations: {
        // 增加
        increase(state) {
            state.count++
        },
        // 減少
        decrease(state) {
            state.count--
        }
    },
    actions: {
        // action函數(shù)接受一個與store實例具有相同方法和屬性的context對象,調(diào)用context.commit 提交一個mutation
        increase(context) {
            context.commit('increase')
        }
    },
    modules: {}
})

action通過store.dispatch觸發(fā)嫌变,action 則會提交 mutation吨艇,mutation 會對 state 進行修改,組件再根據(jù) state 腾啥、getter 渲染頁面东涡。

上邊的action寫法與之前直接提交mutation沒有什么區(qū)別,但是action內(nèi)部可以執(zhí)行異步操作倘待,而mutation只能是同步操作疮跑。

    // 執(zhí)行異步操作
    actions: {
        increas({ commit }) {
            setTimeout(() => {
                commit('increase')
            }, 1000)
        },
        decrease({ commit }) {
            setTimeout(() => {
                commit('decrease')
            }, 1000)
        }
    }

同時action也是支持荷載方式和對象方式進行分發(fā):

// 以載荷形式分發(fā)
store.dispatch('increase', {
  amount: 10
})

// 以對象形式分發(fā)
store.dispatch({
  type: 'increase',
  amount: 10
})

在組件中使用this.$store.dispatch('xxx') 分發(fā) action,或者使用mapActions輔助函數(shù)將組件的methods映射為 store.dispatch 調(diào)用延柠,

<template>
  <div>
    <input type="button" value="+" @click="increase" />
    <input type="button" value="-" @click="decrease" />
    <p>計數(shù):{{count}}</p>
  </div>
</template>

<script>
  import {
    mapActions
  } from 'vuex'
  import {
    mapState
  } from 'vuex'
  export default {
    // ...
    methods: {
      ...mapActions([
        'increase', // 將 `this.increase()` 映射為 `this.$store.dispatch('increment')`
      ]),
      ...mapActions({
        decrease: 'decrease' // 將 `this.decrease()` 映射為 `this.$store.dispatch('decrease')`
      })
    },computed:{
      ...mapState(["count"])
    }
  }
</script>

七祸挪、Module

當(dāng)應(yīng)用變得復(fù)雜時,store對象會變得非常臃腫贞间,vuex可以store分割成多個模塊(Module)贿条,每個模塊都擁有自己的state、mutation增热、action整以、getter。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)


const moduleA = {
    state: {
        a: 'a'
    },
    mutations: {},
    actions: {},
    getters: {}
}

const moduleB = {
    state: {
        b: 'b'
    },
    mutations: {},
    actions: {},
    getters: {}
}
export default new Vuex.Store({
    state: {

    },
    getters: {},
    mutations: {},
    actions: {},
    modules: { ma: moduleA, mb: moduleB }
})
<template>
  <div>
    <p>{{msg}}</p>
  </div>
</template>

<script>
  import {
    mapActions
  } from 'vuex'
  import {
    mapState
  } from 'vuex'
  export default {
    computed: {
      msg() {
        return this.$store.state.ma.a; 
      }
    }
  }
</script>

對于模塊內(nèi)部的mutation與getter峻仇,接受的第一個參數(shù)時模塊的局部狀態(tài)對象

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increase (state) {
      // 這里的 `state` 對象是模塊的局部狀態(tài)
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

對于模塊內(nèi)部的action公黑,局部狀態(tài)可以通過context.state暴露出來,根節(jié)點狀態(tài)則為context.rootState

const moduleA = {
  actions: {
    increaseIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

模塊內(nèi)部的getter,根節(jié)點狀態(tài)回作為第三個參數(shù)暴露出來

const moduleA = {
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

命令空間

默認(rèn)情況下凡蚜,mutations人断、actions、getters這些都是注冊在全局上面的朝蜘,可以直接調(diào)用恶迈,希望你的模塊具有更高的封裝度和復(fù)用性,你可以通過添加 namespaced: true 的方式使其成為帶命名空間的模塊谱醇。當(dāng)模塊被注冊后暇仲,它的所有 getter、action 及 mutation 都會自動根據(jù)模塊注冊的路徑調(diào)整命名副渴。

首先新建一個app.js用來聲明模塊

const state = {
    keyword: "",
};
const mutations = {
    SET_KEYWORD(state, payload) {
        state.keyword = payload
    },
    DEL_KEYWORD(state) {
        state.keyword = ""
    }
};
//action可以提交mutation奈附,在action中可以執(zhí)行store.commit,如果要使用某個action煮剧,需執(zhí)行store.dispath
const actions = {
    setKeyword({ commit }, value) {
        commit("SET_KEYWORD", value);
    },
    delKeyword({ commit }) {
        commit("DEL_KEYWORD");
    },

};
export const app = {
    namespaced: true,
    state,
    mutations,
    actions
};

然后在store.js里面引入

import { app } from './app.js';

export default new Vuex.Store({
  modules: {
    app: app
  },
});

使用action 根據(jù)模塊注冊的路徑調(diào)用

store.dispatch('app/delKeyword')

在帶命名空間的模塊內(nèi)訪問全局內(nèi)容(Global Assets)

如果你希望使用全局 state 和 getter斥滤,rootStaterootGetters 會作為第三和第四參數(shù)傳入 getter,也會通過 context 對象的屬性傳入 action轿秧。

若需要在全局命名空間內(nèi)分發(fā) action 或提交 mutation中跌,將 { root: true } 作為第三參數(shù)傳給 dispatchcommit 即可。(官網(wǎng)例子)

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在這個模塊的 getter 中菇篡,`getters` 被局部化了
      // 你可以使用 getter 的第四個參數(shù)來調(diào)用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在這個模塊中, dispatch 和 commit 也被局部化了
      // 他們可以接受 `root` 屬性以訪問根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

平時開發(fā)時只用到了這些一喘,關(guān)于更多的module可以去擼官網(wǎng)驱还。

八、項目結(jié)構(gòu)

平時開發(fā)中使用下面的項目結(jié)構(gòu)

store
    modules
        a.js
        b.js
        c.js
    getters.js
    index.js

a.js示例

const state = {
    keyword: "",
};
const mutations = {
    SET_KEYWORD(state, payload) {
        state.keyword = payload
    },
    DEL_KEYWORD(state) {
        state.keyword = ""
    }
};
const actions = {
    setKeyword({ commit }, value) {
        commit("SET_KEYWORD", value);
    },
    delKeyword({ commit }) {
        commit("DEL_KEYWORD");
    },

};
export default {
    namespaced: true,
    state,
    mutations,
    actions
};

getters.js示例

const getters = {
    keyword: state => state.a.keyword,
};
export default getters;

index.js示例

import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
const path = require("path");

Vue.use(Vuex);

const files = require.context("./modules", false, /\.js$/);
let modules = {};
files.keys().forEach(key => {
  let name = path.basename(key, ".js");
  modules[name] = files(key).default || files(key);
});
const store = new Vuex.Store({
  modules,
  getters
});
export default store;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凸克,一起剝皮案震驚了整個濱河市议蟆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萎战,老刑警劉巖咐容,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚂维,居然都是意外死亡戳粒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門虫啥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔚约,“玉大人,你說我怎么就攤上這事涂籽∑凰睿” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長树枫。 經(jīng)常有香客問我直焙,道長,這世上最難降的妖魔是什么砂轻? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任奔誓,我火速辦了婚禮,結(jié)果婚禮上舔清,老公的妹妹穿的比我還像新娘丝里。我一直安慰自己,他們只是感情好体谒,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布杯聚。 她就那樣靜靜地躺著,像睡著了一般抒痒。 火紅的嫁衣襯著肌膚如雪幌绍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天故响,我揣著相機與錄音傀广,去河邊找鬼。 笑死彩届,一個胖子當(dāng)著我的面吹牛伪冰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播樟蠕,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贮聂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寨辩?” 一聲冷哼從身側(cè)響起吓懈,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎靡狞,沒想到半個月后耻警,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡甸怕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年甘穿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕾各。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡扒磁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出式曲,到底是詐尸還是另有隱情妨托,我是刑警寧澤缸榛,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站兰伤,受9級特大地震影響内颗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜敦腔,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一均澳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧符衔,春花似錦找前、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至形帮,卻和暖如春槽惫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辩撑。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工界斜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人合冀。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓各薇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親君躺。 傳聞我的和親對象是個殘疾皇子得糜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354