Vue 3源码极客之:`Vuex`的`Module`:其`getter`、`mutation`和`action`的命名空间化实现。

各位靓仔靓女,晚上好!我是你们今晚的Vuex专属老司机。今天咱们要聊点刺激的,一起深扒Vuex里Module的命名空间,看看它是怎么把gettermutationaction安排得明明白白的。别害怕,我保证用最骚气的方式,让你们彻底搞懂它!

开场白:Vuex Module的“房间分配术”

话说Vuex就像一栋大楼,里面住着各种各样的数据和操作。如果所有东西都堆在一个房间里,那还不乱成一锅粥?所以,Vuex引入了Module的概念,相当于给每个房间贴上标签,分门别类地管理。而命名空间,就是这房间号,确保每个房间里的gettermutationaction不会撞衫,不会打架。

第一节:Module的创建与注册:领房卡入住

首先,我们得先盖栋房子(Vuex store),然后创建一些房间(Module),最后把它们注册到大楼里。

// 创建一个Vuex store
import { createStore } from 'vuex'

const store = createStore({
  modules: {
    // 注册一个名为 'user' 的 module
    user: {
      // module 的配置项,后面会详细介绍
      namespaced: true, // 开启命名空间
      state: () => ({
        name: '默认用户名',
        age: 18
      }),
      getters: {
        userName: (state) => state.name,
        userAge: (state) => state.age
      },
      mutations: {
        updateName(state, payload) {
          state.name = payload;
        }
      },
      actions: {
        asyncUpdateName({ commit }, payload) {
          return new Promise((resolve) => {
            setTimeout(() => {
              commit('updateName', payload);
              resolve();
            }, 500);
          })
        }
      }
    },
    product: {
      namespaced: true,
      state: () => ({
        productList: []
      }),
      getters: {
        productList: (state) => state.productList
      },
      mutations: {
        setProductList(state, payload) {
          state.productList = payload;
        }
      },
      actions: {
        asyncFetchProductList({ commit }) {
          // 模拟异步请求
          return new Promise((resolve) => {
            setTimeout(() => {
              const mockData = [{ id: 1, name: '产品A' }, { id: 2, name: '产品B' }];
              commit('setProductList', mockData);
              resolve();
            }, 500);
          })
        }
      }
    }
  }
})

export default store

在这个例子中,我们创建了一个Vuex store,并在modules选项中注册了两个模块:userproduct。每个模块都有自己的stategettersmutationsactions。关键的一步是设置了namespaced: true,这意味着这个模块开启了命名空间。

第二节:命名空间的意义:避免“撞衫”惨案

如果没有命名空间,所有模块的gettermutationaction都会挂在全局的Vuex store上。如果两个模块有同名的getter,比如都叫userName,那就会发生“撞衫”惨案,后面的getter会覆盖前面的。

有了命名空间,Vuex会给每个gettermutationaction加上一个前缀,这个前缀就是模块的名称。比如,user模块的userName getter会被命名为user/userName。这样,即使不同的模块有同名的getter,也不会发生冲突。

第三节:Getter的命名空间:精确制导,获取数据

开启命名空间后,访问getter的方式也会发生变化。我们需要使用mapGetters辅助函数,并指定模块的名称。

<template>
  <div>
    <p>User Name: {{ userName }}</p>
    <p>Product List Length: {{ productListLength }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters('user', ['userName']), // 获取user模块的userName getter
    ...mapGetters('product', { // 另一种写法
      productListLength: 'productList' // 将product模块的productList getter 映射为 productListLength
    })
  }
}
</script>

在上面的代码中,我们使用mapGetters辅助函数,并传入模块的名称userproduct。这样,我们就可以精确地获取到指定模块的getter

第四节:Mutation的命名空间:精准打击,修改数据

提交mutation的方式也类似,我们需要使用commit方法,并指定完整的mutation名称(包括模块名称)。

<template>
  <div>
    <button @click="updateUserName">Update User Name</button>
  </div>
</template>

<script>
import { useStore } from 'vuex';
import { onMounted } from 'vue';

export default {
  setup() {
    const store = useStore();

    const updateUserName = () => {
      // 提交user模块的updateName mutation
      store.commit('user/updateName', '新的用户名');
    };

    onMounted(() => {
      console.log(store.getters['user/userName']); // 获取user模块的userName
    });

    return {
      updateUserName
    };
  }
}
</script>

在上面的代码中,我们使用store.commit('user/updateName', '新的用户名')来提交user模块的updateName mutation。

第五节:Action的命名空间:远程操控,执行任务

分发action的方式也一样,我们需要使用dispatch方法,并指定完整的action名称(包括模块名称)。

<template>
  <div>
    <button @click="fetchProductList">Fetch Product List</button>
  </div>
</template>

<script>
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();

    const fetchProductList = async () => {
      // 分发product模块的asyncFetchProductList action
      await store.dispatch('product/asyncFetchProductList');
      console.log(store.getters['product/productList']);
    };

    return {
      fetchProductList
    };
  }
}
</script>

在上面的代码中,我们使用store.dispatch('product/asyncFetchProductList')来分发product模块的asyncFetchProductList action。

第六节:辅助函数mapActionsmapMutations:偷懒神器

就像mapGetters一样,Vuex也提供了mapActionsmapMutations辅助函数,让我们更方便地使用actionmutation

<template>
  <div>
    <button @click="updateUserName('新的用户名')">Update User Name</button>
    <button @click="fetchProductList">Fetch Product List</button>
  </div>
</template>

<script>
import { mapActions, mapMutations } from 'vuex';

export default {
  methods: {
    ...mapMutations('user', ['updateName']), // 将user模块的updateName mutation映射为updateName方法
    ...mapActions('product', { //另一种写法
      fetchProductList: 'asyncFetchProductList' // 将product模块的asyncFetchProductList action映射为fetchProductList方法
    })
  }
}
</script>

使用mapActionsmapMutations后,我们可以直接在模板中调用actionmutation,而不需要手动调用dispatchcommit

第七节:createNamespacedHelpers:模块专用工具包

如果你在一个组件中频繁使用某个模块的gettermutationaction,那么可以使用createNamespacedHelpers函数创建一个模块专用的辅助函数工具包。

<template>
  <div>
    <p>User Name: {{ userName }}</p>
    <button @click="updateName('新的用户名')">Update User Name</button>
  </div>
</template>

<script>
import { createNamespacedHelpers } from 'vuex';

const { mapGetters, mapMutations } = createNamespacedHelpers('user');

export default {
  computed: {
    ...mapGetters(['userName'])
  },
  methods: {
    ...mapMutations(['updateName'])
  }
}
</script>

createNamespacedHelpers函数接收一个模块名称作为参数,并返回一个包含mapGettersmapActionsmapMutations函数的对象。这样,我们就可以直接使用这些函数,而不需要每次都指定模块名称。

第八节:动态注册模块:即插即用,灵活扩展

Vuex还支持动态注册模块,这意味着我们可以在运行时添加新的模块。这在某些情况下非常有用,比如根据用户的权限动态加载不同的模块。

// 动态注册一个模块
store.registerModule('permission', {
  namespaced: true,
  state: () => ({
    permissions: []
  }),
  mutations: {
    setPermissions(state, payload) {
      state.permissions = payload;
    }
  },
  actions: {
    asyncFetchPermissions({ commit }) {
      // 模拟异步请求
      return new Promise((resolve) => {
        setTimeout(() => {
          const mockData = ['read', 'write', 'delete'];
          commit('setPermissions', mockData);
          resolve();
        }, 500);
      })
    }
  }
});

// 使用动态注册的模块
store.dispatch('permission/asyncFetchPermissions');
console.log(store.getters['permission/permissions']);

使用store.registerModule方法可以动态注册一个模块。第一个参数是模块的名称,第二个参数是模块的配置项。

第九节:模块的嵌套:构建更复杂的结构

模块还可以嵌套,这意味着我们可以在一个模块中注册其他的模块。这可以帮助我们构建更复杂的应用结构。

const store = createStore({
  modules: {
    account: {
      namespaced: true,
      state: () => ({
        userInfo: {
          name: '张三',
          email: '[email protected]'
        }
      }),
      modules: {
        settings: {
          namespaced: true,
          state: () => ({
            theme: 'light',
            language: 'zh-CN'
          }),
          mutations: {
            setTheme(state, payload) {
              state.theme = payload;
            }
          }
        }
      }
    }
  }
});

// 访问嵌套模块的 state
console.log(store.state.account.userInfo.name);

// 提交嵌套模块的 mutation
store.commit('account/settings/setTheme', 'dark');

// 访问嵌套模块的 getter (需要加上完整的路径)
// 如果settings模块有getter,那么需要通过store.getters['account/settings/getterName'] 访问

命名空间总结:

特性 描述
目的 避免不同模块之间的gettermutationaction命名冲突。
开启方式 在模块配置中设置namespaced: true
访问getter 使用mapGetters辅助函数,并指定模块名称。或者使用store.getters['模块名/getter名']
提交mutation 使用store.commit('模块名/mutation名', payload)。或者使用mapMutations辅助函数。
分发action 使用store.dispatch('模块名/action名', payload)。或者使用mapActions辅助函数。
辅助函数 mapGettersmapActionsmapMutationscreateNamespacedHelpers
动态注册模块 使用store.registerModule('模块名', moduleConfig)
嵌套模块 模块可以嵌套,形成更复杂的结构。访问嵌套模块的stategettermutationaction需要加上完整的路径。

结束语:Vuex Module的命名空间,你学会了吗?

好了,各位靓仔靓女,今天的Vuex Module命名空间之旅就到这里了。希望通过这次深入浅出的讲解,你们能够彻底掌握Vuex Module的命名空间,写出更优雅、更健壮的Vue应用。记住,命名空间就像房间号,它可以让你的Vuex store井井有条,避免“撞衫”惨案。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注