深海鱼的博客 深海鱼的博客
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

深海鱼

尽人事,知天命,知行合一。
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 导读
  • vue2

  • vuex

    • 前言
    • 调式环境准备
    • 源码基本结构与install函数
    • ModuleCollection与Module类
    • Store实例化
    • Store实例方法
    • 辅助函数
      • 一、工具函数
        • 1. normalizeMap
        • 2. normalizeNamespace
        • 3. getModuleByNamespace
      • 二、辅助函数
        • 1. mapState
        • 2. mapGetters
        • 3. mapMutations
        • 4. mapActions
        • 5. createNamespacedHelpers
    • 内置插件
    • 总结与常见问题
  • vue-router

  • vue3

  • react

  • Axios

  • 源码解读
  • vuex
深海鱼
2024-07-01
目录

辅助函数

Vuex为我们提供了几个辅助函数,便于我们在项目中使用store实例的状态数据。函数列表如下:

  • mapState: 映射store的state属性到组件的computed属性中。
  • mapGetters:映射store的getters属性到组件的computed属性中。
  • mapActions:映射store的actions属性到组件的methods属性中。
  • mapMutations:映射store的mutations属性到组件的methods属性中。
  • createNamespacedHelpers:创建一个带命名空间的辅助函数,它们在使用前都已经绑定在了给定的命名空间。

以下是一个使用mapState的例子:

import { mapState } from 'vuex'
export default {
  computed: mapState({
    count: state => state.count, // 普通使用,$store.state.count
    countAlias: "count", // $store.state.count
  })
}
1
2
3
4
5
6
7

# 一、工具函数

在讲解辅助函数之前,我们先来看看几个辅助函数用到的工具函数。

# 1. normalizeMap

normalizeMap用于将给定的对象转换成一个包含键值对的数组,定义如下:

function normalizeMap(map) {
  return Array.isArray(map) // 是数组
    ? map.map(key => ({ key, val: key })) // 将数组转为[{key: 元素, val: 元素}]
    // 将对象转为[{ key: key, val: val }],非Object会返回空数组
    : Object.keys(map).map(key => ({ key, val: map[key] })) 
}
1
2
3
4
5
6

如下例子:

normalizeMap({
  count: state => state.count,
  countAlias: "count",
}) 
// => [{ key: "count", val: state => state.count }, { key: "countAlias", val: "count" }]

normalizeMap(['count', 'name', 'age']) 
// => [{ key: "count", val: "count" }, { key: "name", val: "name" }, { key: "age", val: "age" }]

1
2
3
4
5
6
7
8
9

# 2. normalizeNamespace

normalizeNamespace接收一个函数,并且返回一个接收namespace和map作为参数的新函数。返回的新函数会对namespace和map进行参数规范化,并将规范化后的参数传递给原始函数执行,返回结果。定义如下:

function normalizeNamespace(fn) {
  return (namespace, map) => {
    // 参数规范化
    if (typeof namespace !== 'string') {
      // namespace不是字符串则将namespace赋值给map后置空
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      // namespace以/结尾
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 如果namespace不是字符串,则将namespace赋值给map,namespace置空。
  • 如果namespace是字符串且不以/结尾,则在结尾添加/
  • 返回一个接收格式化后的namespace和map作为参数的新函数。

# 3. getModuleByNamespace

getModuleByNamespace函数根据指定的namespace从stores实例中获取模块,获取到则返回,否则输出错误。定义如下:

function getModuleByNamespace(store, helper, namespace) {
  const module = store._modulesNamespaceMap[namespace] // 从模块映射表中获取模块
  if (process.env.NODE_ENV !== 'production' && !module) {
    // 获取不到输出错误
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}
1
2
3
4
5
6
7
8

可见,获取模块时会从store._modulesNamespaceMap中查找获取,通过前面的文章我们可以知道,带有命名空间的模块会被添加到store._modulesNamespaceMap中,store._modulesNamespaceMap是一个以命名空间为key,模块实例为值的对象。

# 二、辅助函数

所有的辅助函数都是调用normalizeNamespace生成的一个函数接收namespace和map两个参数并的新函数。执行时会对其进行参数规范化,然后调用normalizeMap函数将map转换为[{key, val}]数组,最后再遍历处理后返回结果,其返回结果是一个包含键值对的对象,因此可以使用扩展运算符...将结果展开。

# 1. mapState

export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  // 将states转为[{key, val}]后遍历
  normalizeMap(states).forEach(({ key, val }) => {
    // res[key]是一个函数,因此可以在Vue实例中使用computed的属性访问
    res[key] = function mappedState() {
      let state = this.$store.state // 从当前Vue实例获取$store.state
      let getters = this.$store.getters // 从当前Vue实例获取$store.getters
      if (namespace) {// 带了命名空间
        // 从store._modulesNamespaceMap中获取命名空间对应的模块
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        // 从模块中的局部上下文中获取state和getters
        // context是new Store时,执行installModule时生成的context
        state = module.context.state
        getters = module.context.getters
      }
      // val是个function,则以当前Vue实例绑定this后,传入state和getters作为参数执行返回结果
      // 否则返回state[val]
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    // 将当前函数标记为来自vuex,提供给devtools
    res[key].vuex = true
  })
  return res
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

mapState返回一个新函数,函数接收namespace和states作为参数,由normalizeNamespace的分析可知,namespace格式化可能是空字符串,也可能是带/结尾的字符串,states则是通过normalizeNamespace格式化后的map。返回的新函数会执行如下操作:

  • 定义res对象用于存储返回的结果
  • 将states转为[{key, val}]后遍历
  • 令res[key]为一个新函数mappedState,该函数会执行如下操作:
    • 令state为当前Vue实例的$store.state,getters为当前Vue实例的$store.getters
    • 如果namespace存在,则从store._modulesNamespaceMap中获取命名空间对应的模块,如果找不到模块则返回undefined,否则令state和getters为模块中的局部上下文context的state和getters
    • 如果val是个函数,则以当前Vue实例绑定this后,传入state和getters作为参数执行返回结果,否则返回state[val]
    • 为res[key]标记vuex属性,提供给devtools使用
  • 返回最终的res对象

所有的辅助函数基本都遵循该处理思路,后文不再赘述,仅对差异做说明,详细见源码注释。

# 2. mapGetters

export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  normalizeMap(getters).forEach(({ key, val }) => {
    val = namespace + val
    res[key] = function mappedGetter() {
      // 不存在的模块
      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return
      }
      // 不存在的getter
      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
        return
      }
      // 从$store.getters中获取
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    // 给devtools的vuex标记
    res[key].vuex = true
  })
  return res
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

mapGetters会根据命名空间直接从store.getters中获取,不会从module.context.getters中获取。

# 3. mapMutations

export const mapMutations = normalizeNamespace((namespace, mutations) => {
  const res = {}
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation(...args) {
      let commit = this.$store.commit
      if (namespace) {
        // 从store._modulesNamespaceMap中获取命名空间对应的模块
        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
        if (!module) {
          return
        }
        // 从模块中的局部上下文中获取commit方法
        commit = module.context.commit
      }

      return typeof val === 'function' // 第二个参数是函数形式:function(commit, ...args) { commit('xxx', ...args) })
        ? val.apply(this, [commit].concat(args)) // 映射为vm.val(commit, ...args)
        : commit.apply(this.$store, [val].concat(args)) // 映射为this.$store.commit(val, ...args)
    }
  })
  return res
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

当val是函数时,commit方法会被作为第一个参数传入,且函数的this会被绑定为当前Vue实例。

import { mapMutations } from 'vuex' 
export default {
  data() {
    return {
      type: "increment"
    }
  }
  methods: {
    ...mapMutations(['increment', 'incrementBy']),
    ...mapMutations({
      inc: function(commit, payload) {
        commit(this.type, payload)
      }
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4. mapActions

export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction(...args) {
      let dispatch = this.$store.dispatch
      if (namespace) {
        // 获取模块
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) { // 不存在的模块
          return
        }
        // 局部上下文中的dispatch
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'// 第二个参数是函数形式:function(dispatch, ...args) { dispatch('xxx', ...args) }
        ? val.apply(this, [dispatch].concat(args)) // 映射为vm.val(dispatch, ...args)
        : dispatch.apply(this.$store, [val].concat(args)) // 映射为this.$store.dispatch(val, ...args)
    }
  })
  return res
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

mapActions和mapMutations的处理方式基本类似。

# 5. createNamespacedHelpers

当我们需要从某个命名空间中获取state、getters、mutation、action时,这时候我们会在上面提供的辅助中,传入namespace参数。但是有时候,我们可能会同时想创建基于同一个命名空间的mutation、action、state、getters,如果每次都传入namespace会较为麻烦,所以vuex提供了createNamespacedHelpers方法,该方法会返回一个对象,该对象包含mapState、mapGetters、mapMutations、mapActions四个基于给定命名空间namespace的辅助绑定函数。其实现就是使用bind方法,预设namespace参数:

export const createNamespacedHelpers = (namespace) => ({
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
  mapActions: mapActions.bind(null, namespace)
})
1
2
3
4
5
6

以上就是Vuex提供的所有辅助函数的实现,所有辅助函数的本质就是生成一个可以访问store的对象供Vue实例的计算属性或者methods使用。

最近更新: 2024/07/01, 18:59
Store实例方法
内置插件

← Store实例方法 内置插件→

最近更新
01
Axios源码解读
07-29
02
基于React16的Webpack升级与构建速度优化
07-09
03
Vue-Router源码解读
07-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024 深海鱼 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式