Vuex白話教程第六講:Vuex的管理員Module(實戰(zhàn)篇)

文 | 大宏

寫在前面

這一講是 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票髓,這里也推薦給大家。

二铣耘、簡單入手

項目的默認結構圖

這里洽沟,我們只看 src 目錄,其他的暫時不管涡拘。組件 components 不在這一講的范圍內玲躯,所以也可以忽視,資源 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

在前言里面华临,我們已經了 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 中已經注入了兩個子模塊 moduleA moduleB,我們可以在 App.vue 中通過 this.$store.state.moduleA.text 這種方式來直接訪問模塊中的 state 數(shù)據(jù)振亮。如下修改:

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

由此可知巧还,模塊內部的 state 是局部的,只屬于模塊本身所有坊秸,所以外部必須通過對應的模塊名進行訪問麸祷。

但是注意了:

模塊內部的 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é)點

我們已經知曉,模塊內部的 state 是局部的印蔬,只屬于模塊本身所有柿菩。那么如果我們要想在模塊中訪問 store 根節(jié)點的數(shù)據(jù) state,怎么辦呢露乏?

很簡單,我們可以在模塊內部的 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ù)已經被我們獲取到了。這里需要注意的是在 getters 中浩聋,rootState 是以第三個參數(shù)暴露出來的观蜗,另外,還有第四個參數(shù) rootGetters赡勘,用來獲得根節(jié)點的 getters 信息嫂便,這里就不演示了,感興趣自己可以去嘗試闸与。唯一要強調的就是千萬不要弄錯參數(shù)的位置了毙替。

當然,action 中也能接收到 rootGetters践樱,但是在 action 中厂画,由于它接收過來的數(shù)據(jù)都被包在 context 對象中的,所以解包出來沒有什么順序的限制拷邢。

五袱院、命名空間

前面我們已經知道了,模塊內部的 action、mutation 和 getter 默認是注冊在全局命名空間的忽洛。如果我們只想讓他們在當前的模塊中生效腻惠,應該怎么辦呢?

通過添加 namespaced: true 的方式使其成為帶命名空間的模塊欲虚。當模塊被注冊后集灌,它的所有 getter、action 及 mutation 都會自動根據(jù)模塊注冊的路徑調整命名复哆。

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

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

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

[vuex] unknown getter: detail

在全局 getter 中已經找不到 detail 的這個方法了梯找,因為它的路勁已經改變了唆阿,不再屬于全局,僅僅只屬于 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 也都是局部化的寄狼,不需要在同一模塊內額外添加空間名前綴丁寄。也就是說,更改 namespaced 屬性后不需要修改模塊內的任何代碼泊愧。

那么我們如何在帶命名空間的模塊內訪問全局內容呢伊磺?

通過前面的學習,我們已經了解到:

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

現(xiàn)在如果想要在全局命名空間內分發(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);
        }
    }
}

接下來看看如何在帶命名空間的模塊內注冊全局 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 是調用的時候所傳入的參數(shù)拨匆,當然也叫載荷。

示例就講到這里挽拂,接下來看看帶命名空間的綁定函數(shù)涮雷。

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

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

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é),官網講得比較清楚储藐,所以直接搬過來了俱济。

在 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 的核心概念就已經全部講完了枉证。不知道大家掌握的如何,雖然這套框架有點抽象移必,但其實只要我們真的用心去學了室谚,要弄懂它也是很容易的。不過光看懂還是不夠崔泵,一定要在項目中多運用秒赤,多實操才能夠掌握的更加牢固。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末憎瘸,一起剝皮案震驚了整個濱河市入篮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幌甘,老刑警劉巖潮售,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锅风,居然都是意外死亡酥诽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門皱埠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肮帐,“玉大人,你說我怎么就攤上這事漱逸±嵋蹋” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵饰抒,是天一觀的道長。 經常有香客問我诀黍,道長袋坑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任眯勾,我火速辦了婚禮枣宫,結果婚禮上,老公的妹妹穿的比我還像新娘吃环。我一直安慰自己也颤,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布郁轻。 她就那樣靜靜地躺著翅娶,像睡著了一般文留。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竭沫,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天燥翅,我揣著相機與錄音,去河邊找鬼蜕提。 笑死森书,一個胖子當著我的面吹牛,可吹牛的內容都是我干的谎势。 我是一名探鬼主播凛膏,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脏榆!你這毒婦竟也來了猖毫?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤姐霍,失蹤者是張志新(化名)和其女友劉穎鄙麦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镊折,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡胯府,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恨胚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骂因。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赃泡,靈堂內的尸體忽然破棺而出寒波,到底是詐尸還是另有隱情,我是刑警寧澤升熊,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布俄烁,位于F島的核電站,受9級特大地震影響级野,放射性物質發(fā)生泄漏页屠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一蓖柔、第九天 我趴在偏房一處隱蔽的房頂上張望辰企。 院中可真熱鬧,春花似錦况鸣、人聲如沸牢贸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潜索。三九已至臭增,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間帮辟,已是汗流浹背速址。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留由驹,地道東北人芍锚。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像蔓榄,于是被迫代替她去往敵國和親并炮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容

  • 使用說明-Vuex 安裝 直接下載 / CDN 引用 Unpkg.com 提供了基于 NPM 的 CDN 鏈接甥郑。以...
    滿是裂縫的花卷閱讀 987評論 0 8
  • 安裝 npm npm install vuex --save 在一個模塊化的打包系統(tǒng)中逃魄,您必須顯式地通過Vue.u...
    蕭玄辭閱讀 2,926評論 0 7
  • 上一章總結了 Vuex 的框架原理,這一章我們將從 Vuex 的入口文件開始澜搅,分步驟閱讀和解析源碼伍俘。由于 Vuex...
    你的肖同學閱讀 1,768評論 3 16
  • Vuex 概念篇 Vuex 是什么? Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式勉躺。它采用集中式...
    Junting閱讀 3,062評論 0 43
  • import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(V...
    F_imok閱讀 2,646評論 0 12