mutations vuex 调用_vuex源码解析

本文将以代码 + 注释的方式讲解vuex的实现

在开始之前,我们先看一个vuex的使用举例

import Vue from 'vue'
import Vuex from 'vuex'
import App from './App.vue'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },

  actions: {
    increment({ commit }) {
      commit('increment')
    }
  },

  mutations: {
    increment(state) {
      state.count++
    }
  },

  getters: {
    countGetter: state => state.count
  }
})

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

我们把基本的功能都使用到了,那么接下来我们来看一下vuex中都做了什么

7483be58c6884553ef8291bc4762f4c9.png
vuex初始化过程

vuex的实现过程

  • 使用vue.mixin混入beforeCreated生命周期,注入$store字段赋值vuex.Store实例
  • 初始化用户相关配置(state、actions、mutations、getters、modules)
    • 根节点相关的配置(state、actions、mutations、getters)命名为root
    • modules下相关配置(state、actions、mutations、getters)以树形结构存在root module的 _children 内
    • 所有的module挂载到 store 实例的 _modulesNamespaceMap 字段
    • actions、mutations、getters以函数名 + 命名空间为key,具体函数为value分别挂载到 store 实例的 _actions、_mutations、_wrappedGetters 字段可以直接访问
  • 初始化用户响应式vm实例
    • 通过vue.computed计算属性,实现用户getter方法(每次调用都会动态计算state)

vuex注册

在上面的举例中,首先我们注册了vuex插件

Vue.use(Vuex)

Vue.use 会调用 Vuex 的 install 方法(vue开发插件),我们看一下 vuex 的 install 的方法实现:

// 文件路径:/src/store.js => install 函数
let Vue // bind on install Vue实例

export function install (_Vue) {
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

我们注意到 vuex 中使用到一个 Vue 全局字段,在 install 的时候会先判断是否重复注册,防止重复注册。

接下来调用了 applyMixin 方法:

// 文件路径:/src/mixin.js
function applyMixin(Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    // vue2.x 执行
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    const options = this.$options // 获取 vue 实例的配置项
    // store injection
    if (options.store) {
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

applyMixin 方法中,首先获取 vue 的版本号,通过这一步,我们可以发现 vuex 分别为 vue1vue2 做了对应的支持。因为现在都在使用 vue2,我们只看 vue2 支持部分,这里使用 Vue.mixin 在 vue 实例的 beforeCreate 生命周期之前执行 vuexInit 方法。

vuexInit 方法的实现:

  • 获取 vue 实例的配置项 vue.$options
  1. 如果配置项中含有 store 字段(开头举例时声明的 store 变量)
    1. 当前 vue 实例(this)赋值 $store 字段为 配置项的 store 字段,如果 store 字段为函数执行赋值返回结果,其他情况直接赋值
  2. 如果配置项不包含 store 字段,且当前实例有父级实例父级实例已声明 $store 字段
    1. 当前 vue 实例(this)赋值 $store 字段为 父级实例的 $store 字段

走到这一步,我们完成了vuex插件的注册操作。

vuex实例化

我们在注册 vuex 插件的注册之后,实例化了 Vuex.Store。

const store = new Vuex.Store({
  state: {
    count: 0
  },

  actions: {
    increment({ commit }) {
      commit('increment')
    }
  },

  mutations: {
    increment(state) {
      state.count++
    }
  },

  getters: {
    countGetter: state => state.count
  }
})

我们看一下 new Vuex.Store 实例化都做什么事情。

// 文件目录:/src/store.js => class Store
// 只保留了关键代码
class Store {
  // 构造函数
  constructor (options = {}) {
    // 当全局 Vue 字段没有被赋值,通过 window.Vue 注册 vuex 插件
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    // 代码省略 --- 这里声明了很多字段,不做多说
    this._modules = new ModuleCollection(options) // 初始化 modules 实例

    // 对用户暴露的dispatch方法
    // 也就是我们常用的this.$store.dispatch
    this.dispatch = function boundDispatch (type, payload) {
      // 使用call方法绑定this
      // 防止this.$store.dispatch出现dispatch函数内部this指向错误
      return dispatch.call(store, type, payload)
    }

    // 对用户暴露的commit方法
    // 也就是我们常用的this.$store.commit
    this.commit = function boundCommit (type, payload, options) {
      // 使用call方法绑定this
      // 防止this.$store.commit出现commit函数内部this指向错误
      return commit.call(store, type, payload, options)
    }

    // 初始化根 module
    installModule(this, state, [], this._modules.root)

    // 初始化 vue 实例负责 vuex 响应式
    resetStoreVM(this, state)

    // 注册插件
    plugins.forEach(plugin => plugin(this))

    // vue开发者工具初始化 - 省略
  }
}

主要步骤:

  • 如果之前未注册 vuex 插件,则注册 vuex 插件
  • 初始化module
  • 初始化dispatch函数,也就是我们常用的this.$store.dispatch
  • 初始化commit函数,也就是我们常用的this.$store.commit
  • 初始化根节点的module
  • 初始化vue响应式
  • 注册插件
  • 初始化vue开发者工具

Module

我们声明的 state、mutations、actions、getters 的组合为一个 Module(常见为命名空间)

// 文件目录:/src/module/module.js
// 具体代码已省略
class Module {
  constructor (rawModule, runtime) {} // 构造函数

  get namespaced () {} // 当前 module 是否已命名空间

  addChild (key, module) {} // 添加子节点 module

  removeChild (key) {} // 移除子节点

  getChild (key) {} // 返回指定子节点

  update (rawModule) {} // 更新 module(更新namespaced、actions、mutations、actions)

  forEachChild (fn) {} // 遍历全部子节点执行回调

  forEachGetter (fn) {} // 遍历全部getters执行回调

  forEachAction (fn) {} // 遍历全部actions执行回调

  forEachMutation (fn) {} // 遍历全部mutations执行回调
}

注册actions、mutations、getters

/**
 * 注册mutation函数
 * params: {
 *   store: store实例
 *   type: mutation函数路径(命名空间地址 + 函数名)
 *   handler: mutation函数
 *   local: 重写dispatch、commit、getters、state,方便直接调用module内部互相调用
 * }
 */
function registerMutation (store, type, handler, local) {
  // mutation使用数组保存(可以声明多个相同路径的mutation)
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    // 使用call方法,保证mutation函数内部this指向了store实例
    // local.state为当前module命名空间的state
    // payload为用户commit时的实参 
    handler.call(store, local.state, payload)
  })
}

/**
 * 注册action函数
 * params: {
 *   store: store实例
 *   type: action函数路径(命名空间地址 + 函数名)
 *   handler: action函数
 *   local: 重写dispatch、commit、getters、state,方便直接调用module内部互相调用
 * }
 */
function registerAction (store, type, handler, local) {
  // action使用数组保存(可以声明多个相同路径的action)
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    // 使用call方法,保证action函数内部this指向了store实例
    let res = handler.call(store, {
      dispatch: local.dispatch, // 当前module命名空间的dispatch
      commit: local.commit, // 当前module命名空间的commit
      getters: local.getters, // 当前module命名空间的getters
      state: local.state, // 当前module命名空间的state
      rootGetters: store.getters, // 根节点的getters
      rootState: store.state // 根节点的state
    }, payload, cb)
    if (!isPromise(res)) {
      // 始终保证action返回的结果为一个promise对象
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

/**
 * 注册getter函数
 * params: {
 *   store: store实例
 *   type: getter函数路径(命名空间地址 + 函数名)
 *   rawGetter: getter函数
 *   local: 重写dispatch、commit、getters、state,方便直接调用module内部互相调用
 * }
 */
function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  // 注意:getter与actions、mutations不同,同一个type是唯一的
  store._wrappedGetters[type] = function wrappedGetter (store) {
    // 执行getter函数
    return rawGetter(
      local.state, // 当前module命名空间的state
      local.getters, // 当前module命名空间的getters
      store.state, // 根节点的state
      store.getters // 根节点的getters
    )
  }
}

dispatch函数

// 文件目录:/src/store => class Store => dispatch
/**
 * _type: 调用action路径
 * _payload: 调用action参数
 */
dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type, // 调用action路径
      payload // 调用action参数
    } = unifyObjectStyle(_type, _payload) // 兼容处理_type为Object的情况

    const action = { type, payload }
    const entry = this._actions[type] // 取出对应的action函数
    if (!entry) {
      // 不存在action终止执行
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    try {
      // before dispatch 生命周期遍历回调
      this._actionSubscribers
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn(`[vuex] error in before action subscribers: `)
        console.error(e)
      }
    }

    // 遍历执行actio函数
    // vuex在注册action时,内部将函数返回的结果使用promise包裹
    // 保证返回的肯定是一个promise对象
    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload))) // 多个函数使用Prmoise.all
      : entry[0](payload)

    return result.then(res => {
      try {
        // after dispatch 生命周期遍历回调
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (process.env.NODE_ENV !== 'production') {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      // 返回结果,这里注意如果配置了多个相同的action,返回的是一个结果集合
      return res 
    })
  }

Commit函数

// 文件目录:/src/store => class Store => commit
/**
 * _type: 调用commit路径
 * _payload: 调用commit参数
 * _options: commit配置参数
 */
commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options) // 兼容处理_type为Object的情况

    const mutation = { type, payload }
    const entry = this._mutations[type] // 取出对应的mutation函数
    if (!entry) {
      // 不存在_mutations终止执行
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    this._withCommit(() => {
      // 遍历执行mutation函数
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    // 遍历调用 subscribe 生命周期回调
    this._subscribers.forEach(sub => sub(mutation, this.state))
  }

工具函数

helper函数

// 文件目录:/src/helpers.js
// 帮助函数

// 将数组、对象转为map格式
// 例:normalizeMap(['test']) => { key: 'test', val: 'test' }
// 例:normalizeMap({ test: 1 }) => { key: 'test', val: 1 }
function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

// 格式化namespace
function normalizeNamespace (fn) {
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      // 当传入第一个参数不为string时,默认认为是从根节点访问
      map = namespace
      namespace = ''
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      // namespace最后一位不是 '/' 为namespace添加 '/' 结尾
      namespace += '/' // 从 namespace 开始访问
    }
    return fn(namespace, map)
  }
}

// 通过namespace获取module
// store:store实例
// helper:开发模式时打日志使用
// namespace:namespace访问路径
function getModuleByNamespace (store, helper, namespace) {
  // 通过_modulesNamespaceMap访问
  // _modulesNamespaceMap在installModule函数中注册
  const module = store._modulesNamespaceMap[namespace]
  if (process.env.NODE_ENV !== 'production' && !module) {
    console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
  }
  return module
}

mapState函数 & mapGetters函数

使用示例

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  }),
  ...mapGetters({
    a: 'a',
    b: 'b'
  })
}

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  }),
  ...mapGetters({
    a: 'a',
    b: 'b'
  })
}

实现代码:mapState

// 文件目录:/src/helpers.js
const mapState = normalizeNamespace((namespace, states) => {
  const res = {}
  // 格式化states遍历
  normalizeMap(states).forEach(({ key, val }) => {
    res[key] = function mappedState () {
      // 这里this指向vue实例
      let state = this.$store.state // store根节点state
      let getters = this.$store.getters // store根节点getters
      if (namespace) {
        // 如果用户指定了namespace则取对应的module
        const module = getModuleByNamespace(this.$store, 'mapState', namespace)
        if (!module) {
          return
        }
        // module.context在installModul初始化(上文的local)
        state = module.context.state // 取module的state
        getters = module.context.getters // 取module的getter
      }
      return typeof val === 'function'
        ? val.call(this, state, getters) // 如果传入的是函数,则返回执行结果
        : state[val] // 直接返回对应字段
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res // 返回结果集合
})

实现代码:mapGetters

// 文件目录:/src/helpers.js
const mapGetters = normalizeNamespace((namespace, getters) => {
  const res = {}
  // 格式化states遍历
  normalizeMap(getters).forEach(({ key, val }) => {
    val = namespace + val // 拼接namespace
    res[key] = function mappedGetter () {
      // 不存在的module终止
      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
      }
      // 返回getter
      return this.$store.getters[val]
    }
    // mark vuex getter for devtools
    res[key].vuex = true
  })
  return res // 返回结果集合
})

mapMutations函数 & mapActions函数

使用示例

methods: {
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ]),
  ...mapMutations([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ]),
  ...mapMutations([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

实现代码:mapActions

// 文件目录:/src/helpers.js
const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
  // 格式化actions遍历
  normalizeMap(actions).forEach(({ key, val }) => {
    res[key] = function mappedAction (...args) {
      // get dispatch function from store
      let dispatch = this.$store.dispatch // 先取根节点的dispatch
      if (namespace) {
        // 不存在的module终止
        const module = getModuleByNamespace(this.$store, 'mapActions', namespace)
        if (!module) {
          return
        }
        dispatch = module.context.dispatch // 取module的dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [dispatch].concat(args))
        : dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
})

实现代码:mapMutations

// 文件目录:/src/helpers.js
const mapMutations = normalizeNamespace((namespace, mutations) => {
  const res = {}
  // 格式化actions遍历
  normalizeMap(mutations).forEach(({ key, val }) => {
    res[key] = function mappedMutation (...args) {
      // Get the commit method from store
      let commit = this.$store.commit // 先取根节点的dispatch
      if (namespace) {
        // 不存在的module终止 
        const module = getModuleByNamespace(this.$store, 'mapMutations', namespace)
        if (!module) {
          return
        }
        commit = module.context.commit // 取module的dispatch
      }
      return typeof val === 'function'
        ? val.apply(this, [commit].concat(args))
        : commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值