Vuex 核心概念(二):Module

156 阅读3分钟

什么是 Module?

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿,为了解决以上问题,Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块。

模块化的实现

// user 模块
const user = {
  state() {
    return {
      username: 'kobe',
    }
  },
  getters: {
    showUsername(state) {
      return '用户名:' + state.username
    }
  },
  mutations: {
    increment(state) {
      console.log('user increment')
    }
  },
  actions: {
    incrementAction(context) {
      console.log('user incrementAction');
    }
  },
}
// home 模块
const home = {
  state() {
    return {
      token: 'qqwfdsfsdtgfgdfgdsgergdfs',
    }
  },
  getters: {
    showToken(state) {
      return 'token 值:' + state.token
    }
  },
  mutations: {
    increment(state) {
      console.log('home increment');
    }
  },
  actions: {
    incrementAction(context) {
      console.log('home incrementAction');
    }
  },
}
// store 实例
const store = createStore({
  state() {
    return {
      counter: 21
    }
  },
  getters: {
    showCounter(state) {
      return '当前计数:' + state.counter
    }
  },
  mutations: {
    increment(state) {
      console.log('store increment');
    }
  },
  actions: {
    showCounterAction(context) {
      console.log(context.state.counter)
    },
    incrementAction(context) {
      console.log('store incrementAction');
    }
  },
  modules: {
    user,
    home,
  },
})

模块化的状态获取

子模块里面的状态是以对象的方式跟 store 实例里的状态合并的。 相当于:

state() {
  return {
    counter: 21, 
    user: {
      username: 'kobe'
    },
    home: {
      token: 'qqwfdsfsdtgfgdfgdsgergdfs',
    }
  }
}

所以获取子模块里的状态,要先获取到对应的模块,再来获取:

<div>{{ $store.state.counter }}</div>
<div>{{ $store.state.user.username }}</div>
<div>{{ $store.state.home.token }}</div>

image.png

模块化的 getters

子模块里的 gettersstore 实例里的 getters 是直接合并的。如果重名会直接覆盖(属性的重新赋值)。相当于:

getters: {
  showCounter(state) {
    return '当前计数:' + state.counter
  },
  showUsername(state) {
    return '用户名:' + state.username
  },
  showToken(state) {
    return 'token 值:' + state.token
  }
}

所以子模块的 getters 可以直接使用:

<div>{{ $store.getters.showCounter }}</div>
<div>{{ $store.getters.showUsername }}</div>
<div>{{ $store.getters.showToken }}</div>

image.png

模块化的 mutations

子模块里的 mutationsstore 实例里的 mutations 也是直接合并的。如果重名的话都会保留(事件监听)。相当于:

mutations: {
  increment(state) {
    console.log('store increment')
  },
  increment(state) {
    console.log('user increment')
  },
  increment(state) {
    console.log('home increment')
  }
}

如果我们提交 increment 这个 mutation

this.$store.commit('increment')

image.png

可以看到控制台打印了三次,这是因为所有同名的 mutation 都保留下来了,而且是按照导入的顺序保留的。

模块化的 actions

mutations 相同,子模块里的 actionsstore 实例里的 actions 也是直接合并的,如果重名的话都会保留。 相当于:

actions: {
  incrementAction(state) {
    console.log('store incrementAction')
  },
  incrementAction(state) {
    console.log('user incrementAction')
  },
  incrementAction(state) {
    console.log('home incrementAction')
  }
}
this.$store.dispatch('incrementAction')

image.png

命名空间 namespaced

默认情况下,模块内部的 getteractionmutation 是注册在全局的命名空间中的。我们可以添加 namespaced: true 的方式使其成为带命名空间的模块,当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。

基本使用

// user 模块
const user = {
  namespaced: true;
  state() {
    return {
      username: 'kobe',
    }
  },
  getters: {
    showUsername(state) {
      return '用户名:' + state.username
    }
  },
  mutations: {
    increment(state) {
      console.log('user increment')
    }
  },
  actions: {
    incrementAction(context) {
      context.commit('increment')
    }
  },
}

这时如果想要使用 user 中的 getteractionmutation 就需要调整路径:

this.$store.getters['user/showUsername']
this.$store.commit('user/increment')
this.$store.dispatch('user/incrementAction')

如果在访问时没有带上路径,默认是访问 store 实例中对应的 getteractionmutation

子模块的 action

如果是在子模块中的 action 中提交当前模块中的 mutation 或者是分发其他action的话,是不用加上模块路径的。

mutations: {
  increment(state) {
    console.log('user increment')
  }
},
actions: {
    showUsernameAction(context) {
      console.log(123)
    },
    incrementAction(context) {
      context.commit('increment')
      context.dispatch('showUsernameAction')
    }
}
this.$store.dispatch('user/incrementAction')

image.png

如果子模块想提交 store 实例中的 mutation 或者分发 store 实例中的 action,加上一个参数就可以了。

actions: {
  incrementAction(context) {
    context.commit('increment', null, { root: true })
    context.dispatch('showCounterAction', null, { root: true })
  }
}

image.png

在子模块的 action中,只能提交当前模块或者 store 实例中的 mutationgetter 也是相同,只能使用当前模块或者 store 实例中的 getter

命名空间的好处

命名空间的目的是为了增加代码的可阅读性,它实际上是在对应的属性名前面加上了模块的名称,这么做的好处有两个,第一是我们在使用的时候就能直观的知道对应的是哪个模块,更方便我们维护。第二是如果模块之间或者模块与 store 实例之间存在重名的属性,使用命名空间就不会出现问题,因为模块里的属性需要加上模块名才能被使用。

模块化时出现重名的情况是怎么处理的?

state: 没有影响,因为子模块里面的状态是以对象的方式跟 store 实例里的状态合并的。

getters:子模块里的 gettersstore 实例里的 getters 直接合并

  • 如果是 store 实例里的 getters 与子模块的 getters 重名,以 store 实例为主。
  • 如果是子模块之间重名,以先导入的为主。

mutations