Vuex源碼分析

Vuex安裝

目錄結構vue

import Vue from 'vue'
import Vuex from 'vuex'
//註冊vuex
Vue.use(Vuex)
複製代碼
//安裝時執行 install方法
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)
}
複製代碼
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])
//版本判斷
  if (version >= 2) {
    // vuexInit  
    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
    // store injection  每一個組件都有store的實例
    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
    }
  }
}
複製代碼

vuex註冊時執行install方法,在 install 方法中, 調用了applyMixin 方法,applyMixin方法主要確保每一個組件都有store的實例,均可以使用store實例react

Store實例化

export class Store {
  constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    //不經過npm開發代碼
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      //手動執行
      install(window.Vue)
    }
    //非生產環境
    if (process.env.NODE_ENV !== 'production') {
      //註冊完才能實例化
      assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
    //當前環境是否支持promise
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      //判斷this是vuex的一個實例
      assert(this instanceof Store, `store must be called with the new operator.`)
    }

    const {
      //vue支持的插件
      plugins = [],
      strict = false
    } = options

    // store internal state
    //實例store上的私有屬性
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    //初始化modules的過程
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      //上下文爲store
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict
//根
    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    //actions,mutation 。。作賦值
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    //getter,state創建依賴關係  響應式
    resetStoreVM(this, state)

    // apply plugins
    //遍歷plugins
    plugins.forEach(plugin => plugin(this))

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }
...
}
複製代碼

store實例化過程主要定義私有屬性的初始化,確保 dispatch/commit 方法中的 this 對象正確指向 storevuex

Modules初始化

install把倉庫拆分紅小倉庫,註冊mutation,action,getter遞歸創建樹形數據結構, installModule 接收5個參數: store、rootState、path、module、hot. store 表示當前 Store 實例, rootState 表示根 state, path 表示當前嵌套模塊的路徑數組, module 表示當前安裝的模塊npm

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
//makeLocalContext  設置上下文
  const local = module.context = makeLocalContext(store, namespace, path)
//作mutation 註冊
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    //建立mutation數組 
    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.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}
複製代碼

語法糖

//支持數組或對象   調用map方法  最後都會變成key value數組
function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

 //執行
function normalizeNamespace (fn) {
  //兩個參數 模塊會傳namespace  對兩個參數,一個參數的作相應處理
  return (namespace, map) => {
    if (typeof namespace !== 'string') {
      map = namespace
      namespace = ''
      //不傳第一個參數,自動拼接斜線
    } else if (namespace.charAt(namespace.length - 1) !== '/') {
      namespace += '/'
    }
    return fn(namespace, map)
  }
}
複製代碼
export const mapActions = normalizeNamespace((namespace, actions) => {
  const res = {}
    //遍歷key value
  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
})
複製代碼

該方法會將 store 中的 dispatch 方法映射到組件的 methods 中數組

動態註冊modules

//動態注入新的modules  
  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]
//path不能爲0 只能作擴展 
    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }
//從新對modules作擴展
    this._modules.register(path, rawModule)
    //把新的模塊的mutation,action 擴展進去
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }
//註銷
  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    if (process.env.NODE_ENV !== 'production') {
      assert(Array.isArray(path), `module path must be a string or an Array.`)
    }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    //作新modules 註銷 ,從新整理action ,mutation ,,,
    resetStore(this)
  }
複製代碼
相關文章
相關標籤/搜索