VueX源碼分析(2)


VueX源碼分析(2)

剩余內容

  • /module
  • /plugins
  • helpers.js
  • store.js

helpers要從底部開始分析比較好。也即先從輔助函數開始再分析那4個map函數mapState

helpers.js

getModuleByNamespace

/**
 * Search a special module from store by namespace. if module not exist, print error message.
 * @param {Object} store
 * @param {String} helper
 * @param {String} namespace
 * @return {Object}
 */
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
}

解析:

  • 通過namespace來尋找module,如果找不到打印錯誤信息(開發環境)
  • _modulesNamespaceMap這個Map存有所有的module
  • 在vuex中,不同的作用域用'/'來分隔開的(嵌套模塊),如商城中的購物車的namespace可以這樣表示'shop/shopping_cart'

normalizeMap

/**
 * Normalize the map
 * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
 * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
 * @param {Array|Object} map
 * @return {Object}
 */
function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

解析:

  • 將數組或者對象轉化成[Map, Map]的格式,Map關鍵字有{ key, val }
  • 如果是數組,生成Map的key === val
  • 如果是對象,生成Map的key就是對象的鍵名,val就是對象的值

normalizeNamespace

/**
 * Return a function expect two param contains namespace and map. it will normalize the namespace and then the param's function will handle the new namespace and the map.
 * @param {Function} fn
 * @return {Function}
 */
function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}

解析:

  • 這里的fn就是mapState等4大map函數,使用柯里化緩存fn
  • typeof namespace !== 'string'第一個判斷是支持兩種傳參模式:1、可以不傳namespace直接傳map,如mapActions(['action']);2、支持傳namespace,如mapActions('shop', ['action'])
  • 也即namespace可傳可不傳,不傳最后初始化namespace = ''
  • 如果傳了namespace,要檢查最后一個字符帶不帶'/',沒有則補全
  • 這個函數就是在執行mapState、mapAction等4大map函數之前的namespace預處理,最終才把namesapce和map傳個fn函數

createNamespacedHelpers

/**
 * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
 * @param {String} namespace
 * @return {Object}
 */
export const createNamespacedHelpers = (namespace) => ({
  mapState: mapState.bind(null, namespace),
  mapGetters: mapGetters.bind(null, namespace),
  mapMutations: mapMutations.bind(null, namespace),
  mapActions: mapActions.bind(null, namespace)
})

解析:

  • 這個bind函數涉及到柯里化,要理解柯里化才可理解這個意思
  • 柯里化和函數的參數個數有關,可以簡單把柯里化理解成是一個收集參數的過程,只有收集夠函數所需的參數個數,才會執行函數體,否則返回一個緩存了之前收集的參數的函數。
  • 4大map函數都要接受兩個參數,namespace和map
  • 由柯里化:mapState函數有2個參數,要收集夠2個參數才會執行mapState的函數體
  • createNamespacedHelpers的作用是讓mapState收集第一個參數namespace,由於還差一個參數map,所以返回的是一個緩存了namespace參數的函數,繼續接收下一個參數map
  • 所以被createNamespacedHelpers返回的mapState只需傳入1個參數map就可以執行了,且傳入的第一個參數必須是map,因為namespace已經收集到了,再傳入namespace最終執行的結果會是mapState(namespace, namespace)
  • 總之,如果了解過柯里化,這里應該很好理解。

mapState、mapMutations、mapActions、mapGetters

mapState

/**
 * Reduce the code which written in Vue.js for getting the state.
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
 * @param {Object}
 */
export const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      let state = this.$store.state
      let getters = this.$store.getters
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        state = module.context.state
        getters = module.context.getters
      }
      return typeof val === 'function'
        ? val.call(this, state, getters)
        : state[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

解析:

  • 4大map函數最終結果都是返回一個對象{}
  • mappedState其實就是computed的屬性的函數,看這個函數要聯想到computed,且這個函數的this也是指向vue的
  • 上面的this.$state.statethis.$state.getters是全局的stategetters
  • 接下來就是判斷是不是模塊,是則拿到模塊的state和getter。有種情況用到mapState({ name: (state, getter) => state.name })
  • 最后返回 val 。如果是函數,如上面那樣要先執行一遍,再返回函數執行后的值
  • 因為mappedState就是computed中屬性的函數,一定是要返回值的。
  • res是個對象,所以可以{ computed: { ...mapState(['name', 'age']) } }
// mapState(['name', 'age']) 
const res = {
  // { key, val } 其中: key = 'name' val = 'name'

  name: function mappedState () {
    // 沒有命名空間的情況
    // 這個函數要用到computed的,這里this指向Vue組件實例
    return this.$store.state[name]
  },
  age: function mappedState () {
    // 如果有命名空間的情況
    // 如上面源碼根據namespace拿到模塊module
    const state = module.context.state
    return state[age]
  }
}

// mapState({ name: (state, getter) => state.name })
const res = {
  // { key, val } 其中:key = 'name' val = (state, getter) => state.name

  name: function mappedState () {
    // 沒有命名空間
    // 如上面代碼一樣{ key, val }中的 val = (state, getter) => state.name }
    const state = this.$store.state
    cosnt getter = this.$store.getter
    // this 是指向Vue組件實例的
    return val.call(this, state, getter)
  }
}

mapMutations

/**
 * Reduce the code which written in Vue.js for committing the mutation
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept anthor params. You can commit mutation and do any other things in this function. specially, You need to pass anthor params from the mapped function.
 * @return {Object}
 */
export const mapMutations = normalizeNamespace((namespace, mutations) => {
  const res = {}
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation (...args) {
      // Get the commit method from store
      let commit = this.$store.commit
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
        if (!module) {
          return
        }
        commit = module.context.commit
      }
      return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

解析:

  • 這里也要判斷是不是模塊,不同情況的commit不同,是用全局的還是用模塊的
  • mappedMutationmethods的函數,this同樣指向Vue的實例
  • val.apply(this, [commit].concat(args)),是這種情況mapMutations({ mutationName: (commit, ...arg) => commit('自定義') })
  • commit.apply(this.$store, [val].concat(args)),是這種情況mapMutations(['CHANGE_NAME'])使用的時候還可以傳參數this['CHANGE_NAME'](name)

mapGetters

/**
 * Reduce the code which written in Vue.js for getting the getters
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} getters
 * @return {Object}
 */
export const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  normalizeMap(getters).forEach(({ key, val }) => {
    // thie namespace has been mutate by normalizeNamespace
    val = namespace + val
    res[key] = function mappedGetter () {
      if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) {
        console.error(`[vuex] unknown getter: ${val}`)
        return
      }
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res
})

解析:

  • val = namespace + val這里是,不管是模塊的getter還是全局的getter最終都存在一個地方中($store.getters),是模塊的會有'/,所以這里要補充namespace + val
  • 所以最后返回的是this.$store.getters[val]
  • 還有mappedGetter對應computed屬性的函數,this指向Vue實例

mapActions

/**
 * Reduce the code which written in Vue.js for dispatch the action
 * @param {String} [namespace] - Module's namespace
 * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, You need to pass anthor params from the mapped function.
 * @return {Object}
 */
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      let dispatch = this.$store.dispatch
      if (namespace) {
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

解析:

  • 這個和mapMutations差不多,只是commit換成了dispatch
  • mappedAction對應methods的屬性的函數,this也是指向Vue實例


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM