Vuex源码分析

9 篇文章 0 订阅
7 篇文章 0 订阅

Vuex源码分析

入口文件

Vuex的入口文件为src/index.js

import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

export default {
  Store,
  install,
  version: '__VERSION__',
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers
}

从入口文件可以看到,Vuex向外暴露出了Store、install以及mapState、mapMutations等工具方法。

install

由于Vuex是Vue的一个插件,根据Vue插件开发的规则,要向外暴露出一个install方法,当使用Vue.use(Vuex)的时候就会去执行该install方法,下面来看下Vuex的install方法。

export function install (_Vue) {
  // 防止重复安装Vuex
  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)
}

在install主要做了两件事,一个是防止Vuex被重复安装,另一个则是调用applyMixin通过mixin的形式去初始化Vuex。

applyMixin
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])
  // 对Vue1和Vue2版本做兼容,由于vue1中没有mixin,所以只能通过重写_init方法实现在Vue初始化时初始化Vuex
  if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  function vuexInit () {
    const options = this.$options
    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
    }
  }
}

在vuexInit方法中,组件会尝试从options中获取store,如果当前组件是根组件,则直接将我们传入的store赋值给this. s t o r e , 如 果 当 前 组 件 非 根 组 件 , 则 通 过 o p t i o n s 中 的 p a r e n t 获 取 父 组 件 的 s t o r e 引 用 , 这 样 一 来 , 所 有 的 组 件 都 获 取 到 了 同 一 个 S t o r e 实 例 , 于 是 我 们 可 以 在 每 一 个 组 件 中 通 过 t h i s . store,如果当前组件非根组件,则通过options中的parent获取父组件的store引用, 这样一来,所有的组件都获取到了同一个Store实例,于是我们可以在每一个组件中通过this. storeoptionsparentstoreStorethis.store访问全局的Store实例。

Store

我们传入根组件的store就是通过调用Vuex暴露的Store类来初始化的,下面看一下Store类的实现。

构造函数
constructor (options = {}) {
    // 在某些环境下自动安装Vuex
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    // 判断是否满足Vuex运行的条件
    if (process.env.NODE_ENV !== 'production') {
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
    }

    const {
      // 插件列表
      plugins = [],
      // 是否启用严格模式,在严格模式下直接修改state会抛出异常
      strict = false
    } = options

    this._committing = false  // commit标志变量,用于确保只能通过mutation来修改state
    this._actions = Object.create(null) // 存储actions
    this._actionSubscribers = []  // actions订阅列表,在dispatch action的时候会触发这些方法
    this._mutations = Object.create(null) // 存储mutations
    this._wrappedGetters = Object.create(null)  // 存储getter
    // 创建并收集所有的module,会创建一个root module以及其下的子module
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null) // 存储namespace
    this._subscribers = []  // mutation订阅列表,在commmit mutation的时候会触发这些方法
    this._watcherVM = new Vue() // watcher的Vue实例

    const store = this
    const { dispatch, commit } = this
    // 封装dispatch和commit,并绑定到this上
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    this.strict = strict

    const state = this._modules.root.state

    // 将module中的state、action、mutation安装到store中
    installModule(this, state, [], this._modules.root)

    resetStoreVM(this, state)

    // 初始化plugin
    plugins.forEach(plugin => plugin(this))

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    // 配置了useDevtools就安装devtoolPlugin
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }

在Store的构造函数中主要就是初始化了一些变量以及执行了installModule和resetStoreVM。

installModule
function installModule (store, rootState, path, module, hot) {
  // 根据path判断是否为root Module
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // 若有namespace则在_modulesNamespaceMap中注册
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    // 设置state树,把子module的state放到父module的state(parentState)下,最后就可以构建出完整层级的state树
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
  // makeLocalContext返回一个local对象(简化的store),里面包含适配namespace的commit和dispatch方法,
  // 并同时使用Object.defineProperty将getter、state绑定到local上
  const local = module.context = makeLocalContext(store, namespace, path)

  // 注册mutation  
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })
  // 注册action  
  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })
  // 注册getter
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
  // 若当前module下包含子module,递归注册子module
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}
makeLocalContext
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''
  // 根据namespace使用不同的dispatch和commit
  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args
      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
          console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
          return
        }
      }

      return store.dispatch(type, payload)
    },
    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
          console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
          return
        }
      }
      store.commit(type, payload, options)
    }
  }
  // 定义getters、state到local上
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })
  return local
}

resetStoreVM
function resetStoreVM (store, state, hot) {
  const oldVm = store._vm

  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  // 把getter绑定到store上,让我们可以通过this.$store.getters.xxxgetters的形式访问store._vm[xxxgetters]
  forEachValue(wrappedGetters, (fn, key) => {
    computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true
    })
  })

  const silent = Vue.config.silent
  Vue.config.silent = true
  // 创建新的Vue实例,将state注册data上,这样当state改变时,
  // 就可以利用Vue的响应式原理触发依赖state的组件更新
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  if (store.strict) {
    // 若使用严格模式,则无法直接修改store中的state
    enableStrictMode(store)
  }
  // 解除旧vm的state的引用,以及销毁旧的Vue对象
  if (oldVm) {
    if (hot) {
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

resetStoreVM会遍历store._wrappedGetters,使用Object.defineProperty方法为每一个getter绑定上get方法,让我们可以通过this.$store.getters.xxxgetters的形式访问store._vm[xxxgetters]。之后Vuex采用了new一个Vue实例来实现响应式state,其本质就是将我们传入的state作为一个隐藏的vue组件的data,也就是说,我们的commit操作,本质上其实是修改这个组件的data值,这也就是为什么Vuex中的state要遵循Vue的响应式原则的原因。最后清除了旧的_vm的state并销毁了_vm实例。

dispatch
dispatch (_type, _payload) {
    // 参数校验
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    // 取出对应type的action数组
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }
    // 执行所有的action订阅
    this._actionSubscribers.forEach(sub => sub(action, this.state))

    // 判断entry中action是否超过一个,若超过一个则使用Promise.all执行entry中所有的action
    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }
commit
commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }
    // 使用_withCommit来修改state
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    // 触发订阅mutation里的方法
    this._subscribers.forEach(sub => sub(mutation, this.state))

    if (
      process.env.NODE_ENV !== 'production' &&
      options && options.silent
    ) {
      console.warn(
        `[vuex] mutation type: ${type}. Silent option has been removed. ` +
        'Use the filter functionality in the vue-devtools'
      )
    }
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值