vue中復用vuex.store對象的定義
復用vuex.store對象的定義
我有幾個組件,都需要用到vuex.store,而且這幾個組件結構極其類似,就在想,能不能復用store的對象定義?因為每個組件都定義一遍,繁瑣,且沒必要。
主要是用到克隆組件。具體代碼如下:
1. 共用的store定義
/src/store/common.js
export default { ? namespaced: true, ? state: { ? ? v: "hello store", // for test ? ? items: [], ? }, ? getters: { ? ? v: (state) => state.v, ? ? items: (state) => { ? ? ? return state.items; ? ? }, ? }, ? mutations: { ? ? // 同步操作 ? ? setV(state, val) { ? ? ? state.v = val; ? ? }, ? ? setItems(state, val) { ? ? ? state.items = val; ? ? }, ? }, ? actions: { ? ? keepItems: ({ commit }, val) => { ? ? ? commit("setItems", val); ? ? }, ? }, };
2. 組件1
/src/store/component1.js
import cloneDeep from "lodash/cloneDeep"; import common from "./common"; const category = cloneDeep(common); export default component1;
3. 組件2
/src/store/component2.js
import cloneDeep from "lodash/cloneDeep"; import common from "./common"; const category = cloneDeep(common); export default component2;
vuex中的store
1. Vuex是什么
在了解Store之前,我們先來看看Vuex是個什么東西。Vuex本質上就是一個Vue.js的插件,是用于對復雜應用進行狀態(tài)管理用的,打印Vuex以后輸出:
{ ? ? Store: function Store(){}, ? ? mapActions: function(){}, // 對應Actions的結果集 ? ? mapGetters: function(){}, // 對應Getters的結果集 ? ? mapMutations: function(){}, // 對應Mutations的結果集 ? ? mapState: function(){}, // 對應State的結果集 ? ? install: function install(){}, ? ? installed: true ? }
Vuex和單純的全局對象有以下兩點不同:
- Vuex的狀態(tài)存儲是響應式的。當Vue 組件從 Store 中讀取狀態(tài)的時候,若 Store 中的狀態(tài)發(fā)生變化,那么相應的組件也會相應地得到高效更新。
- 不能直接改變 Store 中的狀態(tài)。改變Store 中的狀態(tài)的唯一途徑就是顯式地提交 mutation。
2. Store
每一個Vuex應用的核心就是Store(倉庫),我們可以說Store是一個容器,Store里面的狀態(tài)與單純的全局變量是不一樣的,無法直接改變Store中的狀態(tài)。想要改變Store中的狀態(tài),只有一個辦法,顯式地提交mutation。
3. 一個完整的Store結構
const store = new Vuex.Store({ ? state: { ? ? // 存放狀態(tài) ? }, ? getters: { ? ? // state的計算屬性 ? }, ? mutations: { ? ? // 更改state中狀態(tài)的邏輯,同步操作 ? }, ? actions: { ? ? // 提交mutation,異步操作 ? }, ? // 如果將store分成一個個的模塊的話,則需要用到modules。 ? ?//然后在每一個module中寫state, getters, mutations, actions等。 ? modules: { ? ? a: moduleA, ? ? b: moduleB, ? ? // ... ? } });
4. 狀態(tài)管理的幾個核心概念
1. state
state是狀態(tài)數(shù)據(jù),可以通過this.$store.state來直接獲取狀態(tài),也可以利用vuex提供的mapState輔助函數(shù)將state映射到計算屬性(computed)中去。用data接收的值不能及時響應更新,用computed就可以:
export default { ? data () { ? ? return { ? ? ? dataCount: this.$store.state.count //用data接收 ? ? } ? }, ? computed:{ ? ? count(){ ? ? ? return this.$store.state.count //用computed接收 ? ? } ? } }
mapState 輔助函數(shù):
mapState是state的語法糖,當一個組件需要獲取多個狀態(tài)時候,將這些狀態(tài)都聲明為計算屬性會有些重復和冗余。為了解決這個問題,我們可以使用 mapState 輔助函數(shù)幫助我們生成計算屬性,讓你少按幾次鍵:
// 在單獨構建的版本中輔助函數(shù)為 Vuex.mapState import { mapState } from 'vuex' ? export default { ? // ... ? computed: mapState({ ? ? // 箭頭函數(shù)可使代碼更簡練 ? ? count: state => state.count, ? ? ? // 傳字符串參數(shù) 'count' 等同于 `state => state.count` ? ? countAlias: 'count', ? ? ? // 為了能夠使用 `this` 獲取局部狀態(tài),必須使用常規(guī)函數(shù) ? ? countPlusLocalState (state) { ? ? ? return state.count + this.localCount ? ? } ? }) }
當映射的計算屬性的名稱與 state 的子節(jié)點名稱相同時,我們也可以給 mapState 傳一個字符串數(shù)組。
computed: mapState([ ? // 映射 this.count 為 store.state.count ? 'count' ])
2. getter
getters本質上是用來對狀態(tài)進行加工處理。Getters與State的關系,就像Vue.js的computed與data的關系。getter 的返回值會根據(jù)它的依賴被緩存起來,且只有當它的依賴值發(fā)生了改變才會被重新計算??梢酝ㄟ^this.$store.getters.valueName對派生出來的狀態(tài)進行訪問。或者直接使用輔助函數(shù)mapGetters將其映射到本地計算屬性中去。
mapGetters 輔助函數(shù):
mapGetters 輔助函數(shù)僅僅是將 store 中的 getter 映射到局部計算屬性:
import { mapGetters } from 'vuex' export default { ? // ... ? computed: { ? // 使用對象展開運算符將 getter 混入 computed 對象中 ? ? ...mapGetters([ ? ? ? 'doneTodosCount', ? ? ? 'anotherGetter', ? ? ? // ... ? ? ]) ? } }
mapGetters實際上是一個方法Vuex對象上的一個方法,這從本文開頭打印的那個Vuex對象的內容可以看出來。…這個符號是ES2015的一個新的語法糖,即將mapGetters處理后的內容展開后填入。
如果你想將一個 getter 屬性另取一個名字,使用對象形式:
mapGetters({ ? // 映射 `this.doneCount` 為 `store.getters.doneTodosCount` ? doneCount: 'doneTodosCount' })
3. mutation
mutations的中文意思是“變化”,利用它可以更改狀態(tài)。事實上,更改 Vuex 的 store 中的狀態(tài)的唯一方法就是提交 (commit)mutation。不過,mutation觸發(fā)狀態(tài)改變的方式有一點特別,所謂commit一個mutation,實際是像觸發(fā)一個事件一樣,傳入一個mutation的類型以及攜帶一些數(shù)據(jù)(稱作payload,載荷)。
mutations: { ? //放置mutations方法 ?? ?increment(state, payload) { ?? ??? ?//在這里改變state中的數(shù)據(jù) ?? ??? ?state.count = payload.number; ?? ?} },
那commit一個mutation在代碼層面怎么表示呢?
this.$store.commit('increment', { ? amount: 10 }) //或者這樣 this.$store.commit({ ? type: 'increment', ? amount: 10 })
除了這種使用 this.$store.commit('xxx') 提交 mutation的方式之外,還有一種方式,即使用 mapMutations 輔助函數(shù)將組件中的 methods 映射為 this.$store.commit。例如:
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')` ? ? }) ? } }
經(jīng)過這樣的映射之后,就可以通過調用方法的方式來觸發(fā)其對應的(所映射到的)mutation commit了,比如,上例中調用add()方法,就相當于執(zhí)行了this.$store.commit('increment')了。
考慮到觸發(fā)的mutation的type必須與mutations里聲明的mutation名稱一致,比較好的方式是把這些mutation都集中到一個文件(如mutation-types)中以常量的形式定義,在其它地方再將這個文件引入,便于管理。而且這樣做還有一個好處,就是整個應用中一共有哪些mutation type可以一目了然。就像下面這樣:
// 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: { ? ? // 我們可以使用 ES2015 風格的計算屬性命名功能來使用一個常量作為函數(shù)名 ? ? [SOME_MUTATION] (state) { ? ? ? // mutate state ? ? } ? } })
4. action
action可以提交mutation,在action中可以執(zhí)行store.commit,而且action中可以有任何的異步操作:
const store = new Vuex.Store({ ? state: { ? ? count: 0 ? }, ? mutations: { ? ? increment (state) { ? ? ? state.count++ ? ? } ? }, ? actions: { ? ? increment (context) { ? ? ? context.commit('increment') ? ? } ? } })
或者用ES2015的參數(shù)解構,可以簡寫成:
actions: { ? ? increment ({commit}) { ? ? ? ?? ?commit('increment') ? ? } }
和mutation類似,我們像上面這樣生命action的處理函數(shù)。它接收的第一個參數(shù)是一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit 提交一個 mutation,或者通過 context.state 和 context.getters 來獲取 state 和 getters。
不過,mutation處理函數(shù)中所做的事情是改變state,而action處理函數(shù)中所做的事情則是commit mutation。
怎么觸發(fā)action呢?按照Vuex的叫法,這叫分發(fā)(dispatch),我們反正知道它實際上是觸發(fā)的意思就行了。具體的觸發(fā)方法是this.$store.dispatch(actionType, payload)。所傳的兩個參數(shù)一個是要觸發(fā)的action的類型,一個是所攜帶的數(shù)據(jù)(payload),類似于上文所講的commit mutation時所傳的那兩個參數(shù)。具體如下:
// 以載荷形式分發(fā) this.$store.dispatch('incrementAsync', { ? amount: 10 })
或
// 以對象形式分發(fā) this.$store.dispatch({ ? type: 'incrementAsync', ? amount: 10 })
還有一種方法是使用 mapActions 輔助函數(shù)將組件的 methods 映射為 this.$store.dispatch 調用。如下:
import { mapActions } from 'vuex' ? export default { ? // ... ? methods: { ? ? ...mapActions([ ? ? ? 'increment', // 將 `this.increment()` 映射為 `this.$store.dispatch('increment')` ? ? ? ? // `mapActions` 也支持載荷: ? ? ? 'incrementBy' // 將 `this.incrementBy(amount)` 映射為 `this.$store.dispatch('incrementBy', amount)` ? ? ]), ? ? ...mapActions({ ? ? ? add: 'increment' // 將 `this.add()` 映射為 `this.$store.dispatch('increment')` ? ? }) ? } }
另外,還需要知道, this.$store.dispatch 可以處理被觸發(fā)的 action 的處理函數(shù)返回的 Promise,并且 this.$store.dispatch 仍舊返回 Promise。
再來看一些組合性的異步操作:
actions: { ? actionA ({ commit }) { ? ? return new Promise((resolve, reject) => { ? ? ? setTimeout(() => { ? ? ? ? commit('someMutation') ? ? ? ? resolve() ? ? ? }, 1000) ? ? }) ? } }
現(xiàn)在你可以:
$this.store.dispatch('actionA').then(() => { ? // ... })
在另外一個 action 中也可以:
actions: { ? // ... ? actionB ({ dispatch, commit }) { ? ? return dispatch('actionA').then(() => { ? ? ? commit('someOtherMutation') ? ? }) ? } }
最后,如果我們利用 async / await 這個 JavaScript 即將到來的新特性,我們可以像這樣組合 action:
// 假設 getData() 和 getOtherData() 返回的是 Promise actions: { ? async actionA ({ commit }) { ? ? commit('gotData', await getData()) ? }, ? async actionB ({ dispatch, commit }) { ? ? await dispatch('actionA') // 等待 actionA 完成 ? ? commit('gotOtherData', await getOtherData()) ? } }
接著來看一個更加實際的購物車示例,涉及到調用異步 API 和分發(fā)多重 mutation:
actions: { ? checkout ({ commit, state }, products) { ? ? // 把當前購物車的物品備份起來 ? ? const savedCartItems = [...state.cart.added] ? ? // 發(fā)出結賬請求,然后樂觀地清空購物車 ? ? commit(types.CHECKOUT_REQUEST) ? ? // 購物 API 接受一個成功回調和一個失敗回調 ? ? shop.buyProducts( ? ? ? products, ? ? ? // 成功操作 ? ? ? () => commit(types.CHECKOUT_SUCCESS), ? ? ? // 失敗操作 ? ? ? () => commit(types.CHECKOUT_FAILURE, savedCartItems) ? ? ) ? } }
5. module
module是對于store的一種切割。由于Vuex使用的是單一狀態(tài)樹,這樣整個應用的所有狀態(tài)都會集中到一個比較大的對象上面,那么,當應用變得非常復雜時,store 對象就很可能變得相當臃腫!它解決了當state中很臃腫的時候,module可以將store分割成模塊,每個模塊中擁有自己的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)
模塊的局部狀態(tài)
對于每個模塊內部的 mutation 和 getter,接收的第一個參數(shù)就是模塊的局部狀態(tài)對象。
const moduleA = { ? state: { count: 0 }, ? mutations: { ? ? increment (state) { ? ? ? // 這里的 `state` 對象是模塊的局部狀態(tài) ? ? ? state.count++ ? ? } ? }, ? ? getters: { ? ? doubleCount (state) { ? ? ? return state.count * 2 ? ? } ? } }
同樣,對于模塊內部的 action,局部狀態(tài)通過 context.state 暴露出來,根節(jié)點狀態(tài)則為 context.rootState:
const moduleA = { ? // ... ? actions: { ? ? incrementIfOddOnRootSum ({ state, commit, rootState }) { ? ? ? if ((state.count + rootState.count) % 2 === 1) { ? ? ? ? commit('increment') ? ? ? } ? ? } ? } }
對于模塊內部的 getter,根節(jié)點狀態(tài)會作為第三個參數(shù)暴露出來:
const moduleA = { ? // ... ? getters: { ? ? sumWithRootCount (state, getters, rootState) { ? ? ? return state.count + rootState.count ? ? } ? } }
命名空間
默認情況下,模塊內部的 action、mutation 和 getter 是注冊在全局命名空間的——這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。
如果希望你的模塊具有更高的封裝度和復用性,你可以通過添加 namespaced: true 的方式使其成為命名空間模塊。當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據(jù)模塊注冊的路徑調整命名。
例如:
const store = new Vuex.Store({ ? modules: { ? ? account: { ? ? ? namespaced: true, ? ? ? ? // 模塊內容(module assets) ? ? ? state: { ... }, // 模塊內的狀態(tài)已經(jīng)是嵌套的了,使用 `namespaced` 屬性不會對其產(chǎn)生影響 ? ? ? 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。
在命名空間模塊內訪問全局內容(Global Assets)
如果你希望使用全局 state 和 getter,rootState 和 rootGetter 會作為第三和第四參數(shù)傳入 getter,也會通過 context 對象的屬性傳入 action。
若需要在全局命名空間內分發(fā) action 或提交 mutation,將 { root: true } 作為第三參數(shù)傳給 dispatch 或 commit即可。
modules: { ? foo: { ? ? namespaced: true, ? ? ? getters: { ? ? ? // 在這個模塊的 getter 中,`getters` 被局部化了 ? ? ? // 你可以使用 getter 的第四個參數(shù)來調用 `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) { ... } ? ? } ? } }
帶命名空間的綁定函數(shù)
當使用 mapState, mapGetters, mapActions 和 mapMutations 這些函數(shù)來綁定命名空間模塊時,寫起來可能比較繁瑣:
computed: { ? ...mapState({ ? ? a: state => state.some.nested.module.a, ? ? b: state => state.some.nested.module.b ? }) }, methods: { ? ...mapActions([ ? ? 'some/nested/module/foo', ? ? 'some/nested/module/bar' ? ]) }
對于這種情況,你可以將模塊的空間名稱字符串作為第一個參數(shù)傳遞給上述函數(shù),這樣所有綁定都會自動將該模塊作為上下文。于是上面的例子可以簡化為:
computed: { ? ...mapState('some/nested/module', { ? ? a: state => state.a, ? ? b: state => state.b ? }) }, methods: { ? ...mapActions('some/nested/module', [ ? ? 'foo', ? ? 'bar' ? ]) }
而且,你可以通過使用 createNamespacedHelpers 創(chuàng)建基于某個命名空間輔助函數(shù)。它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數(shù):
import { createNamespacedHelpers } from 'vuex' ? const { mapState, mapActions } = createNamespacedHelpers('some/nested/module') ? export default { ? computed: { ? ? // 在 `some/nested/module` 中查找 ? ? ...mapState({ ? ? ? a: state => state.a, ? ? ? b: state => state.b ? ? }) ? }, ? methods: { ? ? // 在 `some/nested/module` 中查找 ? ? ...mapActions([ ? ? ? 'foo', ? ? ? 'bar' ? ? ]) ? } }
給插件開發(fā)者的注意事項
如果你開發(fā)的插件(Plugin)提供了模塊并允許用戶將其添加到 Vuex store,可能需要考慮模塊的空間名稱問題。對于這種情況,你可以通過插件的參數(shù)對象來允許用戶指定空間名稱:
// 通過插件的參數(shù)對象得到空間名稱 // 然后返回 Vuex 插件函數(shù) export function createPlugin (options = {}) { ? return function (store) { ? ? // 把空間名字添加到插件模塊的類型(type)中去 ? ? const namespace = options.namespace || '' ? ? store.dispatch(namespace + 'pluginAction') ? } }
模塊動態(tài)注冊
在 store 創(chuàng)建之后,你可以使用 store.registerModule 方法注冊模塊:
// 注冊模塊 `myModule` store.registerModule('myModule', { ? // ... }) // 注冊嵌套模塊 `nested/myModule` store.registerModule(['nested', 'myModule'], { ? // ... })
之后就可以通過 store.state.myModule 和 store.state.nested.myModule 訪問模塊的狀態(tài)。
模塊動態(tài)注冊功能使得其他 Vue 插件可以通過在 store 中附加新模塊的方式來使用 Vuex 管理狀態(tài)。例如,vuex-router-sync 插件就是通過動態(tài)注冊模塊將 vue-router 和 vuex 結合在一起,實現(xiàn)應用的路由狀態(tài)管理。
你也可以使用 store.unregisterModule(moduleName) 來動態(tài)卸載模塊。注意,你不能使用此方法卸載靜態(tài)模塊(即創(chuàng)建 store 時聲明的模塊)。
模塊重用
有時我們可能需要創(chuàng)建一個模塊的多個實例,例如:
(1)創(chuàng)建多個 store,他們公用同一個模塊
(2)在一個 store 中多次注冊同一個模塊
如果我們使用一個純對象來聲明模塊的狀態(tài),那么這個狀態(tài)對象會通過引用被共享,導致狀態(tài)對象被修改時 store 或模塊間數(shù)據(jù)互相污染的問題。
實際上這和 Vue 組件內的 data 是同樣的問題。因此解決辦法也是相同的——使用一個函數(shù)來聲明模塊狀態(tài)(僅 2.3.0+ 支持):
const MyReusableModule = { ? state () { ? ? return { ? ? ? foo: 'bar' ? ? } ? }, ? // mutation, action 和 getter 等等... }
5. store與$store的區(qū)別
$store 是掛載在 Vue 實例上的(即Vue.prototype),組件也是一個Vue實例,在組件中可使用 this 訪問原型上的屬性。template 中可直接通過 {{ $store.state.userName }} 訪問,等價于 script 中的 this.$store.state.userName。
至于 {{ store.state.userName }},script 中的 data 需聲明過 store 才可訪問。
總之,有以下要注意的:
(1)在功能上:
state
保存的是數(shù)據(jù)getters
是對state進行二次加工action
的處理函數(shù)的功能最終是commit mutationmutation
處理函數(shù)的功能最終是改變state
(2)在流程上:
vue component—-dispatch—->actions—-commit—->mutations—-mutate—->state—-render—->vue component。從而形成閉環(huán)。
(3)輔助方法的映射上:
mapGetters、mapState 都是用在computed聲明里面;
mapActions、mapMutations則都是用在methods聲明里面。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
vue 使用插槽分發(fā)內容操作示例【單個插槽、具名插槽、作用域插槽】
這篇文章主要介紹了vue 使用插槽分發(fā)內容操作,結合實例形式總結分析了vue.js使用單個插槽、具名插槽、作用域插槽相關操作技巧與注意事項,需要的朋友可以參考下2020-03-03Ant Design Vue全局對話確認框(confirm)的回調不觸發(fā)
這篇文章主要介紹了Ant Design Vue全局對話確認框(confirm)的回調不觸發(fā)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07