第六章: Vuex的管理員Module

寫在前面

這一講是 Vuex 基礎篇的最后一講厕隧,也是最為復雜的一講。如果按照官方來的話儒旬,對于新手可能有點難以接受栏账,所以想了下,決定干脆多花點時間栈源,用一個簡單的例子來講解挡爵,順便也復習一下之前的知識點。

首先還是得先了解下 Module 的背景甚垦。我們知道茶鹃,Vuex 使用的是單一狀態(tài)樹,應用的所有狀態(tài)會集中到一個對象中艰亮。如果項目比較大闭翩,那么相應的狀態(tài)數(shù)據(jù)肯定就會更多,這樣的話迄埃,store 對象就會變得相當?shù)挠纺[疗韵,非常難管理。

這就好比一家公司只有老板一個人來管理一樣侄非。如果小公司倒還好蕉汪,公司要是稍微大一點流译,那就麻煩了。這個時候者疤,老板就會成立各大部門福澡,并給各大部門安排一個主管,把管理的任務分派下去驹马,然后有什么事情需要處理的話革砸,只需要跟這幾個主管溝通,由主管再把任務分配下去就行了糯累,這就大大提高了工作效率算利,也減輕了老板的負擔。

那么同樣的道理寇蚊,Module 其實就承擔了部門管理員的角色笔时,而 store 就是老板。理解了這一層仗岸,那么后面就好辦多了,接下來借笙,咱們就一步一步動起手來開始實踐扒怖。

一、準備工作

這里我們使用官方提供的 vue-cli 來建一個項目「vuex-test」业稼。當然盗痒,先得安裝 vue-cli:

npm install -g @vue/cli
# OR
yarn global add @vue/cli

安裝完成后,就可以使用以下命令來創(chuàng)建項目了:

vue create vuex-test

還可以使用圖形化界面來創(chuàng)建:

vue ui

具體關于 vue-cli 的使用方法可以去官方查看低散,戳此進入 俯邓。

項目創(chuàng)建完成以后,找到項目安裝的目錄熔号,并打開控制臺執(zhí)行:

// 先定位到項目的目錄下
cd vuex-test

// 然后安裝 vuex
npm install vuex --save

// 運行一下
npm run serve

運行完成稽鞭,可以打開 http://localhost:8080/ 看下效果。

最后大家找一個自己比較比較喜歡的 IDE 來打開這個項目引镊,方便查看和編輯朦蕴。我個人比較喜歡用 WebStore,這里也推薦給大家弟头。

二吩抓、簡單入手

img

項目的默認結構圖

這里,我們只看 src 目錄赴恨,其他的暫時不管疹娶。組件 components 不在這一講的范圍內(nèi),所以也可以忽視伦连,資源 assets 也沒什么可說的雨饺,就是如果有圖片或者視頻什么的話挣饥,都放在這個文件夾里面就是了。

我們打開 App.vue 文件沛膳,去掉組件的相關代碼扔枫,并編寫一點簡單的 vue 代碼。修改如下:

<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" />
        <h1>{{name}}</h1>
        <button @click="modifyNameAction">修改名字</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: 'Lucy'
            }
        },

        methods: {
            modifyNameAction() {
                this.name = "bighone"
            }
        }
    }
</script>

現(xiàn)在我們引入 Vuex 锹安,用它來管理狀態(tài)數(shù)據(jù)短荐,比如這里的 name。首先在 src 中新建一個 store.js 文件叹哭,并寫下如下熟悉的代碼:

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

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        name: 'Lucy',
    },
    mutations: {
        setName(state, newName) {
            state.name = newName;
        }
    },
    actions: {
        modifyName({commit}, newName) {
            commit('setName', newName);
        }
    }
});

然后忍宋,在 main.js 中導入 store,并全局注入:

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

最后修改 App.vue 中的代碼如下:

<script>
    import {mapState, mapActions} from 'vuex';
    export default {
        computed: {
            ...mapState(['name'])
        },

        methods: {
            ...mapActions(['modifyName']),

            modifyNameAction() {
                this.modifyName('bighone');
            }
        },
    }
</script>

想必弄懂這些代碼风罩,應該都是沒啥問題的糠排,因為這些都是 Vuex 很基礎的知識點,這里實操來簡單回顧一下超升,加深印象入宦。如果看不懂,那證明之前的基礎知識還沒掌握室琢。

三乾闰、引入 Module

在前言里面,我們已經(jīng)了 Module 的基本職責盈滴,那么具體如何使用呢涯肩?

Vuex 允許我們將 store 分割成大大小小的對象,每個對象也都擁有自己的 state巢钓、getter病苗、mutation、action症汹,這個對象我們把它叫做 module(模塊)硫朦,在模塊中還可以繼續(xù)嵌套子模塊、子子模塊 ……

現(xiàn)在在 src 里面建個文件夾烈菌,命名為 module阵幸,然后再里面新建一個 moduleA.js 文件,并編寫如下代碼:

export default {
    state: {
        text: 'moduleA'
    },
    getters: {},
    mutations: {},
    actions: {}
}

如上芽世,再建一個 moduleB.js 文件挚赊,這里就不重復了。

然后打開 store.js 文件济瓢,導入這兩個 module :

import moduleA from './module/moduleA';
import moduleB from './module/moduleB';

export default new Vuex.Store({
    modules: {
        moduleA, moduleB,
    },
    // ...
}

這個時候荠割,store 中已經(jīng)注入了兩個子模塊 moduleA moduleB,我們可以在 App.vue 中通過 this.$store.state.moduleA.text 這種方式來直接訪問模塊中的 state 數(shù)據(jù)。如下修改:

// ...
computed: {
    ...mapState({
        name: state => state.moduleA.text
    }),
},
// ...

由此可知蔑鹦,模塊內(nèi)部的 state 是局部的夺克,只屬于模塊本身所有,所以外部必須通過對應的模塊名進行訪問嚎朽。

但是注意了:

模塊內(nèi)部的 action铺纽、mutation 和 getter 默認可是注冊在全局命名空間的,這樣使得多個模塊能夠對同一 mutation 或 action 作出響應哟忍。

這里以 mutation 的響應為例狡门,給 moduleA 和 moduleB 分別新增一個 mutations,如下:

mutations: {
    setText(state) {
        state.text = 'A'
    }
},

moduleB 和上面一樣锅很,把文本名稱修改一下即可其馏,這里就不重復了。然后回到 App.vue 中爆安,修改如下:

<script>
    import {mapState, mapMutations} from 'vuex';
    export default {
        computed: {
            ...mapState({
                name: state => (state.moduleA.text + '和' + state.moduleB.text)
            }),
        },
        methods: {
            ...mapMutations(['setText']),
            modifyNameAction() {
                this.setText();
            }
        },
    }
</script>

運行然后點擊修改叛复,我們會發(fā)現(xiàn)模塊 A 和 B 中的 text 值都改變了。當然扔仓,action 的用法一模一樣褐奥,大家也可以試試。

如果模塊之間的數(shù)據(jù)有交集的話当辐,那么我們其實就可以通過這種方式抖僵,來同步更新模塊之間的數(shù)據(jù),雖然看起來非常的方便缘揪,但是用的時候可一定要謹慎,這種處理方式一旦沒用好义桂,遇到錯誤找筝,排查起來還是比較有難度的。

四慷吊、訪問根節(jié)點

我們已經(jīng)知曉袖裕,模塊內(nèi)部的 state 是局部的,只屬于模塊本身所有溉瓶。那么如果我們要想在模塊中訪問 store 根節(jié)點的數(shù)據(jù) state急鳄,怎么辦呢?

很簡單堰酿,我們可以在模塊內(nèi)部的 getter 和 action 中疾宏,通過 rootState 這個參數(shù)來獲取。接下來触创,我們給 modelA.js 文件添加一點代碼坎藐。

export default {
    // ...
    getters: {
        // 注意:rootState必須是第三個參數(shù)
        detail(state, getters, rootState) {
            return state.text + '-' + rootState.name;
        }
    },
    actions: {
        callAction({state, rootState}) {
            alert(state.text + '-' + rootState.name);
        }
    }
}

然后修改 App.vue

<script>
    import {mapActions, mapGetters} from 'vuex';
    export default {
        computed: {
            ...mapGetters({
                name: 'detail'
            }),
        },
        methods: {
            ...mapActions(['callAction']),
            modifyNameAction() {
                this.callAction();
            }
        },
    }
</script>

然后運行你會發(fā)現(xiàn),根節(jié)點的數(shù)據(jù)已經(jīng)被我們獲取到了。這里需要注意的是在 getters 中岩馍,rootState 是以第三個參數(shù)暴露出來的碉咆,另外,還有第四個參數(shù) rootGetters蛀恩,用來獲得根節(jié)點的 getters 信息疫铜,這里就不演示了,感興趣自己可以去嘗試双谆。唯一要強調(diào)的就是千萬不要弄錯參數(shù)的位置了壳咕。

當然,action 中也能接收到 rootGetters佃乘,但是在 action 中囱井,由于它接收過來的數(shù)據(jù)都被包在 context 對象中的,所以解包出來沒有什么順序的限制趣避。

五庞呕、命名空間

前面我們已經(jīng)知道了,模塊內(nèi)部的 action程帕、mutation 和 getter 默認是注冊在全局命名空間的住练。如果我們只想讓他們在當前的模塊中生效,應該怎么辦呢愁拭?

通過添加 namespaced: true 的方式使其成為帶命名空間的模塊讲逛。當模塊被注冊后,它的所有 getter岭埠、action 及 mutation 都會自動根據(jù)模塊注冊的路徑調(diào)整命名盏混。

我們在 moduleA.js 中添加 namespaced: true

export default {
    namespaced: true,
    // ...
}

這個時候再去運行代碼惜论,你會發(fā)現(xiàn)如下錯誤:

[vuex] unknown getter: detail

在全局 getter 中已經(jīng)找不到 detail 的這個方法了许赃,因為它的路勁已經(jīng)改變了,不再屬于全局馆类,僅僅只屬于 moduleA 了混聊。所以,這個時候乾巧,如果我們想要訪問它句喜,必須帶上路勁才行。修改 App.vue 如下:

<script>
    import {mapActions, mapGetters} from 'vuex';
    export default {
        computed: {
            ...mapGetters({
                name: 'moduleA/detail'
            }),
        },
        methods: {
            ...mapActions({
                call: 'moduleA/callAction'
            }),
            modifyNameAction() {
                this.call();
            }
        },
    }
</script>

注意沟于,如果一個模塊啟用了命名空間咳胃,那么它里面的 getter 和 action 中收到的 getter,dispatch 和 commit 也都是局部化的社裆,不需要在同一模塊內(nèi)額外添加空間名前綴拙绊。也就是說,更改 namespaced 屬性后不需要修改模塊內(nèi)的任何代碼。

那么我們?nèi)绾?strong>在帶命名空間的模塊內(nèi)訪問全局內(nèi)容呢标沪?

通過前面的學習榄攀,我們已經(jīng)了解到:

如果你希望使用全局 state 和 getter,rootState 和 rootGetter 會作為第三和第四參數(shù)傳入 getter金句,也會通過 context 對象的屬性傳入 action檩赢。

現(xiàn)在如果想要在全局命名空間內(nèi)分發(fā) action 或提交 mutation 的話,那么我們只需要將 將 { root: true } 作為第三參數(shù)傳給 dispatch 或 commit 即可违寞。

export default {
    namespaced: true,
    // ...
    actions: {
        callAction({state, commit, rootState}) {
            commit('setName', '改變', {root: true});
            alert(state.text + '-' + rootState.name);
        }
    }
}

接下來看看如何在帶命名空間的模塊內(nèi)注冊全局 action贞瞒。

若需要在帶命名空間的模塊注冊全局 action,你可添加 root: true趁曼,并將這個 action 的定義放在函數(shù) handler 中军浆。

寫法稍微有點變化,我們來看看挡闰,修改 moduleA.js乒融,如下:

export default {
    namespaced: true,
    // ...
    actions: {
        callAction: {
            root: true,
            handler (namespacedContext, payload) {
                let {state, commit} = namespacedContext;
                commit('setText');
                alert(state.text);
            }
        }
    }
}

簡單解釋下,這里的 namespacedContext 就相當于當前模塊的上下文對象摄悯,payload 是調(diào)用的時候所傳入的參數(shù)赞季,當然也叫載荷。

示例就講到這里奢驯,接下來看看帶命名空間的綁定函數(shù)申钩。

關于 mapState, mapGetters, mapActionsmapMutations 這些函數(shù)如何來綁定帶命名空間的模塊,上面示例代碼中其實已經(jīng)都寫過了瘪阁,這里再看看另外幾種更簡便的寫法撒遣,先看看之前的寫法。

這里就用官方的示例代碼舉例說明:

computed: {
    ...mapState({
        a: state => state.some.nested.module.a,
        b: state => state.some.nested.module.b
    })
},
methods: {
    ...mapActions([
        // -> this['some/nested/module/foo']()
        'some/nested/module/foo', 
        // -> this['some/nested/module/bar']()
        'some/nested/module/bar' 
    ])
}

更優(yōu)雅的寫法:

computed: {
    ...mapState('some/nested/module', {
        a: state => state.a,
        b: state => state.b
    })
},
methods: {
    ...mapActions('some/nested/module', [
        'foo', // -> this.foo()
        'bar' // -> this.bar()
    ])
}

將模塊的空間名稱字符串作為第一個參數(shù)傳遞給上述函數(shù)管跺,這樣所有綁定都會自動將該模塊作為上下文愉舔。

我們還可以通過使用 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'
    ])
  }
}

六、模塊的動態(tài)注冊

這一章節(jié)命迈,官網(wǎng)講得比較清楚贩绕,所以直接搬過來了。

在 store 創(chuàng)建之后壶愤,可以使用 store.registerModule 方法動態(tài)的注冊模塊:

// 注冊模塊 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注冊嵌套模塊 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

之后就可以通過 store.state.myModulestore.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 時聲明的模塊)治力。

在注冊一個新 module 時,你很有可能想保留過去的 state勃黍,例如從一個服務端渲染的應用保留 state宵统。你可以通過 preserveState 選項將其歸檔:store.registerModule('a', module, { preserveState: true })

七覆获、模塊重用

就一點马澈,重用會導致模塊中的數(shù)據(jù) state 被污染,所以和 Vue 中的 data 一樣弄息,也使用一個函數(shù)來申明 state 即可痊班。

const MyReusableModule = {
  state () {
    return {
      foo: 'bar'
    }
  },
  //...
}

寫在最后

演示代碼寫的沒啥邏輯,還請見諒摹量,主要還是為了幫助大家更好的理解 Module 的用法反砌,如果有不理解的地方,歡迎留言告知轴捎。

那么到這里般渡,Vuex 的核心概念就已經(jīng)全部講完了。不知道大家掌握的如何具钥,雖然這套框架有點抽象豆村,但其實只要我們真的用心去學了,要弄懂它也是很容易的骂删。不過光看懂還是不夠掌动,一定要在項目中多運用,多實操才能夠掌握的更加牢固宁玫。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粗恢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子欧瘪,更是在濱河造成了極大的恐慌眷射,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛掖,死亡現(xiàn)場離奇詭異妖碉,居然都是意外死亡,警方通過查閱死者的電腦和手機芥被,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門欧宜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拴魄,你說我怎么就攤上這事冗茸∠疲” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵夏漱,是天一觀的道長豪诲。 經(jīng)常有香客問我,道長麻蹋,這世上最難降的妖魔是什么跛溉? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮扮授,結果婚禮上芳室,老公的妹妹穿的比我還像新娘。我一直安慰自己刹勃,他們只是感情好堪侯,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荔仁,像睡著了一般伍宦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乏梁,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天次洼,我揣著相機與錄音,去河邊找鬼遇骑。 笑死卖毁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的落萎。 我是一名探鬼主播亥啦,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼练链!你這毒婦竟也來了翔脱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤媒鼓,失蹤者是張志新(化名)和其女友劉穎届吁,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绿鸣,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡瓷产,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了枚驻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡株旷,死狀恐怖再登,靈堂內(nèi)的尸體忽然破棺而出尔邓,到底是詐尸還是另有隱情,我是刑警寧澤锉矢,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布梯嗽,位于F島的核電站,受9級特大地震影響沽损,放射性物質(zhì)發(fā)生泄漏灯节。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一绵估、第九天 我趴在偏房一處隱蔽的房頂上張望炎疆。 院中可真熱鬧,春花似錦国裳、人聲如沸形入。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亿遂。三九已至,卻和暖如春渺杉,著一層夾襖步出監(jiān)牢的瞬間蛇数,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工是越, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耳舅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓英妓,卻偏偏與公主長得像挽放,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蔓纠,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容