Vuex 允许我们将 store 分割成模块(module)。
每个模块拥有自己的 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 的状态 store.state.b // -> moduleB 的状态
模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = { state: { count: 0 }, mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } }
同样,对于模块内部的 action,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
:
const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = { // ... getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } }
命名空间
默认情况下 注册在全局命名空间的 这样使得多个模块能够对同一 mutation 或 action 作出响应。
可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。
在带命名空间的模块内访问全局内容(Global Assets)
将 { root: true }
作为第三参数传给 dispatch
或 commit
即可
dispatch('someOtherAction') // -> 'foo/someOtherAction' dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' commit('someMutation') // -> 'foo/someMutation' commit('someMutation', null, { root: true }) // -> 'someMutation'
在带命名空间的模块注册全局 action
若需要在带命名空间的模块注册全局 action,你可添加 root: true
,并将这个 action 的定义放在函数 handler
中。例如:
{ actions: { someOtherAction ({dispatch}) { dispatch('someAction') } }, modules: { foo: { namespaced: true, actions: { someAction: { root: true, handler (namespacedContext, payload) { ... } // -> 'someAction' } } } } }
带命名空间的绑定函数
computed: { ...mapState({ a: state => state.some.nested.module.a, b: state => state.some.nested.module.b }) }, methods: { ...mapActions([ 'some/nested/module/foo', // -> this['some/nested/module/foo']() 'some/nested/module/bar' // -> this['some/nested/module/bar']() ]) }
上面的例子可以简化为【如果有的方法在全局里,有的在模块中,则使用上面未简化版本】
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
创建基于某个命名空间辅助函数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
方法注册模块:
import Vuex from 'vuex' const store = new Vuex.Store({ /* 选项 */ }) // 注册模块 `myModule` store.registerModule('myModule', { // ... }) // 注册嵌套模块 `nested/myModule` store.registerModule(['nested', 'myModule'], { // ... })
保留 state
当你设置
preserveState: true
时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
模块重用
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的
data
是同样的问题
使用一个函数来声明模块状态(仅 2.3.0+ 支持):
const MyReusableModule = { state () { return { foo: 'bar' } }, // mutation, action 和 getter 等等... }
module.js
import Vue from 'vue'; import 'es6-promise/auto' import Vuex from 'vuex'; Vue.use(Vuex); const moduleA = { namespaced: true, state: { name: 'ModuleA', count: 0, price: 0, total: 0, }, getters: { //計算moduleA中的count狀態值與root store中的count狀態值 之間的差值 countDistanceWithRootCount(state, getters, rootState) { return state.count - rootState.count; }, }, mutations: { increment(state) { state.count++; state.total = state.price * state.count; }, setName(state, payload) { state.name = payload.name; }, calculate(state, payload) { state.total = state.price * payload; }, }, actions: { //儅大於主節點的count值時,使用主節點的count useRootCountIfCountOverClocked({state, commit, rootState}) { if (state.count > rootState.count) { commit('calculate', rootState.count); } }, }, }; const moduleStore = new Vuex.Store({ state: { count: 10, }, modules: { accountA: moduleA, }, mutations: { calculate(state, payload) { //root store中同名的calculate方法 console.log('called!'); }, }, }); export default moduleStore;
app.js
/** * First we will load all of this project's JavaScript dependencies which * includes Vue and other libraries. It is a great starting point when * building robust, powerful web applications using Vue and Laravel. */ require('./bootstrap'); window.Vue = require('vue'); // import store from './Vuex/index'; import store from './Vuex/module.js'; /** * * The following block of code may be used to automatically register your * Vue components. It will recursively scan this directory for the Vue * components and automatically register them with their "basename". * * Eg. ./components/ExampleComponent.vue -> <example-component></example-component> */ // const files = require.context('./', true, /.vue$/i) // files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default)) Vue.component('example-component', require('./components/ExampleComponent.vue').default); Vue.component('store-component', require('./components/StoreComponent.vue').default); Vue.component('module', require('./components/ModuleStore.vue').default); /** * Next, we will create a fresh Vue application instance and attach it to * the page. Then, you may begin adding components to this application * or customize the JavaScript scaffolding to fit your unique needs. */ const app = new Vue({ el: '#app', store, });
ModuleStore.vue:
<template> <div> <table> <tr> <th>name</th> <th>count</th> <th>price</th> <th>total</th> </tr> <tr> <td>{{name}}</td> <td>{{count}}</td> <td>{{price}}</td> <td>{{total}}</td> </tr> </table> </div> </template> <script> // import {mapState, mapActions, mapMutations, mapGetters} from 'vuex'; import {createNamespacedHelpers} from 'vuex'; const {mapState, mapActions, mapMutations, mapGetters} = createNamespacedHelpers('accountA'); export default { name: "ModuleStore", data: function () { return { pName: '', pCount: 0, pPrice: 0, pTotal: 0, } }, computed: { // ...mapState('accountA', [ // 'name', // 'count', // 'price', // 'total', // ]), ...mapState([ 'name', 'count', 'price', 'total', ]), ...mapActions([ 'useRootCountIfCountOverClocked', ]), }, } </script> <style scoped> </style>
web.php:
<?php use IlluminateSupportFacadesRoute; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); Route::get('/store-test', function (Request $request) { return view('store'); }); Route::get('/module', function (Request $request) { return view('module'); });