vuex 辅助函数 mapState,mapGetters,mapMutations,mapActions


声明:本文转载自https://my.oschina.net/ahaoboy/blog/1648055,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

辅助函数

Vuex 除了提供我们 Store 对象外,还对外提供了一系列的辅助函数,方便我们在代码中使用 Vuex,提供了操作 store 的各种属性的一系列语法糖,下面我们来一起看一下:

mapState

mapState 工具函数会将 store 中的 state 映射到局部计算属性中。为了更好理解它的实现,先来看一下它的使用示例:

// vuex 提供了独立的构建工具函数 Vuex.mapState import { mapState } from 'vuex' export default {   // ...   computed: mapState({     // 箭头函数可以让代码非常简洁     count: state => state.count,     // 传入字符串 'count' 等同于 `state => state.count`     countAlias: 'count',     // 想访问局部状态,就必须借助于一个普通函数,函数中使用 `this` 获取局部状态     countPlusLocalState (state) {       return state.count + this.localCount     }   }) }

当计算属性名称和状态子树名称对应相同时,我们可以向 mapState 工具函数传入一个字符串数组。

computed: mapState([   // 映射 this.count 到 this.$store.state.count   'count' ])

通过例子我们可以直观的看到,mapState 函数可以接受一个对象,也可以接收一个数组,那它底层到底干了什么事呢,我们一起来看一下源码这个函数的定义:

export function mapState (states) {   const res = {}   normalizeMap(states).forEach(({ key, val }) => {     res[key] = function mappedState () {       return typeof val === 'function'         ? val.call(this, this.$store.state, this.$store.getters)         : this.$store.state[val]     }   })   return res }

函数首先对传入的参数调用 normalizeMap 方法,我们来看一下这个函数的定义:

function normalizeMap (map) {   return Array.isArray(map)     ? map.map(key => ({ key, val: key }))     : Object.keys(map).map(key => ({ key, val: map[key] })) }

这个方法判断参数 map 是否为数组,如果是数组,则调用数组的 map 方法,把数组的每个元素转换成一个 {key, val: key}的对象;否则传入的 map 就是一个对象(从 mapState 的使用场景来看,传入的参数不是数组就是对象),我们调用 Object.keys 方法遍历这个 map 对象的 key,把数组的每个 key 都转换成一个 {key, val: key}的对象。最后我们把这个对象数组作为 normalizeMap 的返回值。

回到 mapState 函数,在调用了 normalizeMap 函数后,把传入的 states 转换成由 {key, val} 对象构成的数组,接着调用 forEach 方法遍历这个数组,构造一个新的对象,这个新对象每个元素都返回一个新的函数 mappedState,函数对 val 的类型判断,如果 val 是一个函数,则直接调用这个 val 函数,把当前 store 上的 state 和 getters 作为参数,返回值作为 mappedState 的返回值;否则直接把 this.$store.state[val] 作为 mappedState 的返回值。

那么为何 mapState 函数的返回值是这样一个对象呢,因为 mapState 的作用是把全局的 state 和 getters 映射到当前组件的 computed 计算属性中,我们知道在 Vue 中 每个计算属性都是一个函数。

为了更加直观地说明,回到刚才的例子:

import { mapState } from 'vuex' export default {   // ...   computed: mapState({     // 箭头函数可以让代码非常简洁     count: state => state.count,     // 传入字符串 'count' 等同于 `state => state.count`     countAlias: 'count',     // 想访问局部状态,就必须借助于一个普通函数,函数中使用 `this` 获取局部状态     countPlusLocalState (state) {       return state.count + this.localCount     }   }) }

经过 mapState 函数调用后的结果,如下所示:

import { mapState } from 'vuex' export default {   // ...   computed: {     count() {       return this.$store.state.count     },     countAlias() {       return this.$store.state['count']     },     countPlusLocalState() {       return this.$store.state.count + this.localCount     }   } }

我们再看一下 mapState 参数为数组的例子:

computed: mapState([   // 映射 this.count 到 this.$store.state.count   'count' ])

经过 mapState 函数调用后的结果,如下所示:

computed: {   count() {     return this.$store.state['count']   } }

mapGetters

mapGetters 工具函数会将 store 中的 getter 映射到局部计算属性中。它的功能和 mapState 非常类似,我们来直接看它的实现:

export function mapGetters (getters) {   const res = {}   normalizeMap(getters).forEach(({ key, val }) => {     res[key] = function mappedGetter () {       if (!(val in this.$store.getters)) {         console.error(`[vuex] unknown getter: ${val}`)       }       return this.$store.getters[val]     }   })   return res }

mapGetters 的实现也和 mapState 很类似,不同的是它的 val 不能是函数,只能是一个字符串,而且会检查 val in this.$store.getters 的值,如果为 false 会输出一条错误日志。为了更直观地理解,我们来看一个简单的例子:

import { mapGetters } from 'vuex' export default {   // ...   computed: {     // 使用对象扩展操作符把 getter 混入到 computed 中     ...mapGetters([       'doneTodosCount',       'anotherGetter',       // ...     ])   } }

经过 mapGetters 函数调用后的结果,如下所示:

import { mapGetters } from 'vuex' export default {   // ...   computed: {     doneTodosCount() {       return this.$store.getters['doneTodosCount']     },     anotherGetter() {       return this.$store.getters['anotherGetter']     }   } }

再看一个参数 mapGetters 参数是对象的例子:

computed: mapGetters({   // 映射 this.doneCount 到 store.getters.doneTodosCount   doneCount: 'doneTodosCount' })

经过 mapGetters 函数调用后的结果,如下所示:

computed: {   doneCount() {     return this.$store.getters['doneTodosCount']   } }

mapActions

mapActions 工具函数会将 store 中的 dispatch 方法映射到组件的 methods 中。和 mapState、mapGetters 也类似,只不过它映射的地方不是计算属性,而是组件的 methods 对象上。我们来直接看它的实现:

export function mapActions (actions) {   const res = {}   normalizeMap(actions).forEach(({ key, val }) => {     res[key] = function mappedAction (...args) {       return this.$store.dispatch.apply(this.$store, [val].concat(args))     }   })   return res }

可以看到,函数的实现套路和 mapState、mapGetters 差不多,甚至更简单一些, 实际上就是做了一层函数包装。为了更直观地理解,我们来看一个简单的例子:

import { mapActions } from 'vuex' export default {   // ...   methods: {     ...mapActions([       'increment' // 映射 this.increment() 到 this.$store.dispatch('increment')     ]),     ...mapActions({       add: 'increment' // 映射 this.add() to this.$store.dispatch('increment')     })   } }

经过 mapActions 函数调用后的结果,如下所示:

import { mapActions } from 'vuex' export default {   // ...   methods: {     increment(...args) {       return this.$store.dispatch.apply(this.$store, ['increment'].concat(args))     }     add(...args) {       return this.$store.dispatch.apply(this.$store, ['increment'].concat(args))     }   } }

mapMutations

mapMutations 工具函数会将 store 中的 commit 方法映射到组件的 methods 中。和 mapActions 的功能几乎一样,我们来直接看它的实现:

export function mapMutations (mutations) {   const res = {}   normalizeMap(mutations).forEach(({ key, val }) => {     res[key] = function mappedMutation (...args) {       return this.$store.commit.apply(this.$store, [val].concat(args))     }   })   return res }

函数的实现几乎也和 mapActions 一样,唯一差别就是映射的是 store 的 commit 方法。为了更直观地理解,我们来看一个简单的例子:

import { mapMutations } from 'vuex' export default {   // ...   methods: {     ...mapMutations([       'increment' // 映射 this.increment() 到 this.$store.commit('increment')     ]),     ...mapMutations({       add: 'increment' // 映射 this.add() 到 this.$store.commit('increment')     })   } }

经过 mapMutations 函数调用后的结果,如下所示:

import { mapActions } from 'vuex' export default {   // ...   methods: {     increment(...args) {       return this.$store.commit.apply(this.$store, ['increment'].concat(args))     }     add(...args) {       return this.$store.commit.apply(this.$store, ['increment'].concat(args))     }   } }

插件

Vuex 的 store 接收 plugins 选项,一个 Vuex 的插件就是一个简单的方法,接收 store 作为唯一参数。插件作用通常是用来监听每次 mutation 的变化,来做一些事情。

在 store 的构造函数的最后,我们通过如下代码调用插件:

import devtoolPlugin from './plugins/devtool'  // apply plugins plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))

我们通常实例化 store 的时候,还会调用 logger 插件,代码如下:

import Vue from 'vue' import Vuex from 'vuex' import createLogger from 'vuex/dist/logger'  Vue.use(Vuex)  const debug = process.env.NODE_ENV !== 'production'  export default new Vuex.Store({   ...   plugins: debug ? [createLogger()] : [] })

在上述 2 个例子中,我们分别调用了 devtoolPlugin 和 createLogger() 2 个插件,它们是 Vuex 内置插件,我们接下来分别看一下他们的实现。

devtoolPlugin

devtoolPlugin 主要功能是利用 Vue 的开发者工具和 Vuex 做配合,通过开发者工具的面板展示 Vuex 的状态。它的源码在 src/plugins/devtool.js 中,来看一下这个插件到底做了哪些事情。

const devtoolHook =   typeof window !== 'undefined' &&   window.__VUE_DEVTOOLS_GLOBAL_HOOK__  export default function devtoolPlugin (store) {   if (!devtoolHook) return    store._devtoolHook = devtoolHook    devtoolHook.emit('vuex:init', store)    devtoolHook.on('vuex:travel-to-state', targetState => {     store.replaceState(targetState)   })    store.subscribe((mutation, state) => {     devtoolHook.emit('vuex:mutation', mutation, state)   }) }

我们直接从对外暴露的 devtoolPlugin 函数看起,函数首先判断了devtoolHook 的值,如果我们浏览器装了 Vue 开发者工具,那么在 window 上就会有一个 __VUE_DEVTOOLS_GLOBAL_HOOK__ 的引用, 那么这个 devtoolHook 就指向这个引用。

接下来通过 devtoolHook.emit('vuex:init', store) 派发一个 Vuex 初始化的事件,这样开发者工具就能拿到当前这个 store 实例。

接下来通过 devtoolHook.on('vuex:travel-to-state', targetState => { store.replaceState(targetState) })监听 Vuex 的 traval-to-state 的事件,把当前的状态树替换成目标状态树,这个功能也是利用 Vue 开发者工具替换 Vuex 的状态。

最后通过 store.subscribe((mutation, state) => { devtoolHook.emit('vuex:mutation', mutation, state) }) 方法订阅 store 的 state 的变化,当 store 的 mutation 提交了 state 的变化, 会触发回调函数——通过 devtoolHook 派发一个 Vuex mutation 的事件,mutation 和 rootState 作为参数,这样开发者工具就可以观测到 Vuex state 的实时变化,在面板上展示最新的状态树。

loggerPlugin

通常在开发环境中,我们希望实时把 mutation 的动作以及 store 的 state 的变化实时输出,那么我们可以用 loggerPlugin 帮我们做这个事情。它的源码在 src/plugins/logger.js 中,来看一下这个插件到底做了哪些事情。

// Credits: borrowed code from fcomb/redux-logger  import { deepCopy } from '../util'  export default function createLogger ({   collapsed = true,   transformer = state => state,   mutationTransformer = mut => mut } = {}) {   return store => {     let prevState = deepCopy(store.state)      store.subscribe((mutation, state) => {       if (typeof console === 'undefined') {         return       }       const nextState = deepCopy(state)       const time = new Date()       const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}`       const formattedMutation = mutationTransformer(mutation)       const message = `mutation ${mutation.type}${formattedTime}`       const startMessage = collapsed         ? console.groupCollapsed         : console.group        // render       try {         startMessage.call(console, message)       } catch (e) {         console.log(message)       }        console.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState))       console.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation)       console.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState))        try {         console.groupEnd()       } catch (e) {         console.log('—— log end ——')       }        prevState = nextState     })   } }  function repeat (str, times) {   return (new Array(times + 1)).join(str) }  function pad (num, maxLength) {   return repeat('0', maxLength - num.toString().length) + num }

插件对外暴露的是 createLogger 方法,它实际上接受 3 个参数,它们都有默认值,通常我们用默认值就可以。createLogger 的返回的是一个函数,当我执行 logger 插件的时候,实际上执行的是这个函数,下面来看一下这个函数做了哪些事情。

函数首先执行了 let prevState = deepCopy(store.state) 深拷贝当前 store 的 rootState。这里为什么要深拷贝,因为如果是单纯的引用,那么 store.state 的任何变化都会影响这个引用,这样就无法记录上一个状态了。我们来了解一下 deepCopy 的实现,在 src/util.js 里定义:

function find (list, f) {   return list.filter(f)[0] }  export function deepCopy (obj, cache = []) {   // just return if obj is immutable value   if (obj === null || typeof obj !== 'object') {     return obj   }    // if obj is hit, it is in circular structure   const hit = find(cache, c => c.original === obj)   if (hit) {     return hit.copy   }    const copy = Array.isArray(obj) ? [] : {}   // put the copy into cache at first   // because we want to refer it in recursive deepCopy   cache.push({     original: obj,     copy   })    Object.keys(obj).forEach(key => {     copy[key] = deepCopy(obj[key], cache)   })    return copy }

deepCopy 并不陌生,很多开源库如 loadash、jQuery 都有类似的实现,原理也不难理解,主要是构造一个新的对象,遍历原对象或者数组,递归调用 deepCopy。不过这里的实现有一个有意思的地方,在每次执行 deepCopy 的时候,会用 cache 数组缓存当前嵌套的对象,以及执行 deepCopy 返回的 copy。如果在 deepCopy 的过程中通过 find(cache, c => c.original === obj) 发现有循环引用的时候,直接返回 cache 中对应的 copy,这样就避免了无限循环的情况。

回到 loggerPlugin 函数,通过 deepCopy 拷贝了当前 state 的副本并用 prevState 变量保存,接下来调用 store.subscribe 方法订阅 store 的 state 的变。 在回调函数中,也是先通过 deepCopy 方法拿到当前的 state 的副本,并用 nextState 变量保存。接下来获取当前格式化时间已经格式化的 mutation 变化的字符串,然后利用 console.group 以及 console.log 分组输出 prevState、mutation以及 nextState,这里可以通过我们 createLogger 的参数 collapsed、transformer 以及 mutationTransformer 来控制我们最终 log 的显示效果。在函数的最后,我们把 nextState 赋值给 prevState,便于下一次 mutation。


作者: ustbhuangyi 
链接:http://www.imooc.com/article/14741
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作

本文发表于2018年03月20日 22:38
(c)注:本文转载自https://my.oschina.net/ahaoboy/blog/1648055,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1815 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1