Vuex的Module(哎,这个太重要了,不得不看啊)

2,660 阅读4分钟

化整为零,循序渐进

没有模块是不行的,就像没有人能够一步搭成金字塔。

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。

当应用变得非常复杂时,你会发现你的Vuex模块变成了一个即将上千行的大模块。

为了解决以上问题,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嘛?当时是让读者当作是自己所在的模块对象去使用,其实并不是,它拥有可以访问父节点状态的属性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
    }
  }
}
  • 命名空间 namespaced

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块(几乎都是这么做的)。

当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

const store = new Vuex.Store({
// 第一层嵌套
  modules: {
    account: {
      namespaced: true,

      // 模块内容
      state: () => ({ ... }), // state本来就是嵌套的, `namespaced` 属性对他无影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin'] 使用方法
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // 第二层嵌套模块
      modules: {
      //没用namaspaced
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> getters['account/profile'] // 所以其实是和父亲层级一样的调用
          }
        },
        //用了namaspaced
        posts: {
          namespaced: true,
          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> getters['account/posts/popular'] // 有自己的层级
          }
        }
      }
    }
  }
})
  • 命名空间怎么访问其他 命名空间 或者 全局空间?

我们可以看到命名空间开启后,嵌套模块就像是“子组件”,那肯定是有办法让他去访问都父级模块的。不然实用性几乎就没用了。

之前,也就是上面那个《模块的局部状态》章节稍微说了说怎么去访问(之说了state一些部分),现在开始详细的解释以下。

modules: {
  foo: {
    namespaced: true,// 记得开启

    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以看到第三个参数是rootState,第四个参数是rootGetters
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'  null是参数,必须填,无参数就填空对象或者null
        
        // 从上面你可以观察到,其实它已经是从父模块触发这个dispatch了,这就简单了。
        // 父级怎么触发子级的就怎么使用,就这样兄弟模块之间的交流就实现了。
        dispatch('someOtherAction/otherModule', null, { root: true }) // -> 'someOtherAction' 
        

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

  • 如何在局部模块注册全局action呢?

若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,
     
      actions: {
        someAction: { // 本来是一个函数,现在变成一个对象
          root: true,  // 必须未true才能注册到全局
          handler (namespacedContext, payload) { ... } // -> 'someAction' // 必须handler名字的函数。,然后此函数与正常函数写法一致。
        }
      }
    }
  }
}
  • 简化辅助函数map...的写法

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()
  ])
}

还剩下一点内容个人觉得和入门无关,就不在赘述增加阅读压力,有兴趣自行移步官网观赏。