#### 二、簡單入手

這里,我們只看 `src` 目錄,其他的暫時不管。組件 `components` 不在這一講的范圍內,所以也可以忽視,資源 `assets` 也沒什么可說的,就是如果有圖片或者視頻什么的話,都放在這個文件夾里面就是了。
我們打開 `App.vue` 文件,去掉組件的相關代碼,并編寫一點簡單的 vue 代碼。修改如下:
~~~xml
<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>
~~~
現在我們引入 Vuex ,用它來管理狀態數據,比如這里的 `name`。首先在 `src` 中新建一個 `store.js` 文件,并寫下如下熟悉的代碼:
~~~jsx
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`,并全局注入:
~~~jsx
import store from './store';
// ...
new Vue({
store,
render: h => h(App),
}).$mount('#app')
~~~
最后修改 `App.vue` 中的代碼如下:
~~~xml
<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(模塊),在模塊中還可以繼續嵌套子模塊、子子模塊 ……
現在在 `src` 里面建個文件夾,命名為 `module`,然后再里面新建一個 `moduleA.js` 文件,并編寫如下代碼:
~~~css
export default {
state: {
text: 'moduleA'
},
getters: {},
mutations: {},
actions: {}
}
~~~
如上,再建一個 `moduleB.js` 文件,這里就不重復了。
然后打開 `store.js` 文件,導入這兩個 module :
~~~jsx
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 數據。如下修改:
~~~jsx
// ...
computed: {
...mapState({
name: state => state.moduleA.text
}),
},
// ...
~~~
由此可知,模塊內部的 state 是局部的,只屬于模塊本身所有,所以外部必須通過對應的模塊名進行訪問。
**但是**注意了:
模塊內部的 action、mutation 和 getter 默認可是注冊在**全局命名空間**的,這樣使得多個模塊能夠對同一 mutation 或 action 作出響應。
這里以 mutation 的響應為例,給 moduleA 和 moduleB 分別新增一個 mutations,如下:
~~~bash
mutations: {
setText(state) {
state.text = 'A'
}
},
~~~
moduleB 和上面一樣,把文本名稱修改一下即可,這里就不重復了。然后回到 `App.vue` 中,修改如下:
~~~xml
<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>
~~~
運行然后點擊修改,我們會發現模塊 A 和 B 中的 `text` 值都改變了。當然,action 的用法一模一樣,大家也可以試試。
如果模塊之間的數據有交集的話,那么我們其實就可以通過這種方式,來同步更新模塊之間的數據,雖然看起來非常的方便,但是用的時候可一定要謹慎,這種處理方式一旦沒用好,遇到錯誤,排查起來還是比較有難度的。
#### 四、訪問根節點
我們已經知曉,模塊內部的 state 是局部的,只屬于模塊本身所有。那么如果我們要想在模塊中訪問 store 根節點的數據 state,怎么辦呢?
很簡單,我們可以在模塊內部的 getter 和 action 中,通過 rootState 這個參數來獲取。接下來,我們給 `modelA.js` 文件添加一點代碼。
~~~cpp
export default {
// ...
getters: {
// 注意:rootState必須是第三個參數
detail(state, getters, rootState) {
return state.text + '-' + rootState.name;
}
},
actions: {
callAction({state, rootState}) {
alert(state.text + '-' + rootState.name);
}
}
}
~~~
然后修改 `App.vue` :
~~~xml
<script>
import {mapActions, mapGetters} from 'vuex';
export default {
computed: {
...mapGetters({
name: 'detail'
}),
},
methods: {
...mapActions(['callAction']),
modifyNameAction() {
this.callAction();
}
},
}
</script>
~~~
然后運行你會發現,根節點的數據已經被我們獲取到了。這里需要注意的是在 getters 中,rootState 是以第三個參數暴露出來的,另外,還有第四個參數 rootGetters,用來獲得根節點的 getters 信息,這里就不演示了,感興趣自己可以去嘗試。唯一要強調的就是千萬不要弄錯參數的位置了。
當然,action 中也能接收到 rootGetters,但是在 action 中,由于它接收過來的數據都被包在 `context` 對象中的,所以解包出來沒有什么順序的限制。
#### 五、命名空間
前面我們已經知道了,模塊內部的 action、mutation 和 getter 默認是注冊在全局命名空間的。如果我們只想讓他們在當前的模塊中生效,應該怎么辦呢?
**通過添加 `namespaced: true` 的方式使其成為帶命名空間的模塊。**當模塊被注冊后,它的所有 getter、action 及 mutation 都會自動根據模塊注冊的路徑調整命名。
我們在 `moduleA.js` 中添加 `namespaced: true`。
~~~cpp
export default {
namespaced: true,
// ...
}
~~~
這個時候再去運行代碼,你會發現如下錯誤:
> \[vuex\] unknown getter: detail
在全局 getter 中已經找不到 `detail` 的這個方法了,因為它的路勁已經改變了,不再屬于全局,僅僅只屬于 moduleA 了。所以,這個時候,如果我們想要訪問它,必須帶上路勁才行。修改 `App.vue` 如下:
~~~xml
<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 會作為第三和第四參數傳入 getter,也會通過 context 對象的屬性傳入 action。
現在如果想要在全局命名空間內分發 action 或提交 mutation 的話,那么我們只需要將 將 `{ root: true }` 作為第三參數傳給 dispatch 或 commit 即可。
~~~dart
export default {
namespaced: true,
// ...
actions: {
callAction({state, commit, rootState}) {
commit('setName', '改變', {root: true});
alert(state.text + '-' + rootState.name);
}
}
}
~~~
接下來看看如何**在帶命名空間的模塊內注冊全局 action**。
> 若需要在帶命名空間的模塊注冊全局 action,你可添加 `root: true`,并將這個 action 的定義放在函數 handler 中。
寫法稍微有點變化,我們來看看,修改 `moduleA.js`,如下:
~~~jsx
export default {
namespaced: true,
// ...
actions: {
callAction: {
root: true,
handler (namespacedContext, payload) {
let {state, commit} = namespacedContext;
commit('setText');
alert(state.text);
}
}
}
}
~~~
簡單解釋下,這里的 `namespacedContext` 就相當于當前模塊的上下文對象,`payload` 是調用的時候所傳入的參數,當然也叫載荷。
示例就講到這里,接下來看看**帶命名空間的綁定函數**。
關于 `mapState, mapGetters, mapActions` 和 `mapMutations` 這些函數如何來綁定帶命名空間的模塊,上面示例代碼中其實已經都寫過了,這里再看看另外幾種更簡便的寫法,先看看之前的寫法。
這里就用官方的示例代碼舉例說明:
~~~ruby
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'
])
}
~~~
更優雅的寫法:
~~~jsx
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
~~~
將模塊的空間名稱字符串作為第一個參數傳遞給上述函數,這樣所有綁定都會自動將該模塊作為上下文。
我們還可以通過使用 `createNamespacedHelpers` 創建基于某個命名空間輔助函數。它返回一個對象,對象里有新的綁定在給定命名空間值上的組件綁定輔助函數:
~~~jsx
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'
])
}
}
~~~
#### 六、模塊的動態注冊
這一章節,官網講得比較清楚,所以直接搬過來了。
> 在 store 創建之后,可以使用 `store.registerModule` 方法動態的注冊模塊:
~~~csharp
// 注冊模塊 `myModule`
store.registerModule('myModule', {
// ...
})
// 注冊嵌套模塊 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
~~~
> 之后就可以通過 `store.state.myModule` 和 `store.state.nested.myModule` 訪問模塊的狀態。
>
> 模塊動態注冊功能使得其他 Vue 插件可以通過在 store 中附加新模塊的方式來使用 Vuex 管理狀態。例如,[`vuex-router-sync`](https://github.com/vuejs/vuex-router-sync) 插件就是通過動態注冊模塊將 vue-router 和 vuex 結合在一起,實現應用的路由狀態管理。
>
> 你也可以使用 `store.unregisterModule(moduleName)` 來動態卸載模塊。注意,你不能使用此方法卸載靜態模塊(即創建 store 時聲明的模塊)。
>
> 在注冊一個新 module 時,你很有可能想保留過去的 state,例如從一個服務端渲染的應用保留 state。你可以通過 `preserveState` 選項將其歸檔:`store.registerModule('a', module, { preserveState: true })`。
#### 七、模塊重用
就一點,重用會導致模塊中的數據 state 被污染,所以和 Vue 中的 data 一樣,也使用一個函數來申明 state 即可。
~~~kotlin
const MyReusableModule = {
state () {
return {
foo: 'bar'
}
},
//...
}
~~~