寫在前面
這一講是 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, mapActions
和 mapMutations
這些函數(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.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 時聲明的模塊)酝蜒。在注冊一個新 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 的核心概念就已經全部講完了枉证。不知道大家掌握的如何,雖然這套框架有點抽象移必,但其實只要我們真的用心去學了室谚,要弄懂它也是很容易的。不過光看懂還是不夠崔泵,一定要在項目中多運用秒赤,多實操才能夠掌握的更加牢固。