VueX源碼分析(5)

VueX源碼分析(5)

最終也是最重要的store.js,該文件主要涉及的內容以下:vue

  • Store類
  • genericSubscribe函數
  • resetStore函數
  • resetStoreVM函數
  • installModule函數
  • makeLocalContext函數
  • makeLocalGetters函數
  • registerMutation函數
  • registerAction函數
  • registerGetter函數
  • enableStrictMode函數
  • getNestedState函數
  • unifyObjectStyle函數
  • install函數
  • let Vue上面這些和這個Vue都在同一個做用域

installreact

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

  • 主要是讓全部組件都能拿到store,在組件生命週期beforeCreate期間給全部組件建立$store
  • 若是是開發環境還會判斷是否使用Vue.use(Vuex)

unifyObjectStyle數組

function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  if (process.env.NODE_ENV !== 'production') {
    assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  }

  return { type, payload, options }
}

解析:緩存

  • 主要處理dispatch('pushTab', payload, options)dispatch({ type: 'pushTab', payload }, options)這種狀況
  • 也即第一個參數能夠是字符串,也能夠是對象,就是由第一個參數決定採用什麼樣的方式傳參。這個函數的目的就是支持不一樣風格的傳參
  • 傳參風格(方式),由傳入的第一個參數類型決定

getNestedState數據結構

function getNestedState (state, path) {
  return path.length
    ? path.reduce((state, key) => state[key], state)
    : state
}

解析:app

  • 和以前模塊module中的path同樣,path爲空數組[]爲非嵌套或根模塊,{ shop: { card: { item: 1 } } }的path爲['shop', 'card', 'item']
  • 就是經過迭代的方式獲取狀態item
  • 這樣作的好處是:對象和數組都支持,若是是數組[{ name: 'getMe' }, { name: 'notMe' }],要獲取'getMe'那麼const path = [0, 'name']

enableStrictMode異步

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

解析:ide

  • $watch就是Vue.$watch,主要功能是:若是啓動了嚴格模式,監控數據狀態的改變是否是經過commit改變的。
  • 若是不是經過commit改變狀態的,在開發模式下會提示。
  • VueX的狀態是存在於一個Vue實例中的_data.$$stateStore._vm能夠解釋VueX爲何叫VueX
  • 能夠說VueXVue的一個實例

genericSubscribe函數

function genericSubscribe (fn, subs) {
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

解析:

  • 訂閱某個觀察者,這裏面subs是觀察者,subs關聯到某個狀態,那個狀態改變,會遍歷subs調用裏面的函數。
  • 返回的函數時能夠取消訂閱的,也即const unsubscribe = genericSubscribe(fn, subs),調用unsubscribe()就能夠取消訂閱

class Store

幾個輔助函數和狀態相關

  • resetStore:重置狀態,相似從新開始遊戲,會從新建立Store
  • resetStoreVM:Vuex的狀態是存放在這個Vue實例的_data中
  • installModule:安裝模塊,註冊模塊
  • makeLocalContext:做用域模塊中定義的getters,actions等的函數都會傳入一個context
  • makeLocalGetters:做用域模塊中定義getters細節
  • registerMutation:模塊中的Mutation註冊
  • registerAction:模塊中的Action註冊
  • registerGetter:模塊中的Getter註冊
let Vue // bind on install

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
    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.`)
      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 = [],
      strict = false
    } = options

    // store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    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) {
      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
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state)

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

    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

  get state () {
    return this._vm._data.$$state
  }

  set state (v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `use store.replaceState() to explicit replace store state.`)
    }
  }

  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
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    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'
      )
    }
  }

  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    this._actionSubscribers.forEach(sub => sub(action, this.state))

    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }

  subscribe (fn) {
    return genericSubscribe(fn, this._subscribers)
  }

  subscribeAction (fn) {
    return genericSubscribe(fn, this._actionSubscribers)
  }

  watch (getter, cb, options) {
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

  replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

  registerModule (path, rawModule, options = {}) {
    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.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }

    this._modules.register(path, rawModule)
    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])
    })
    resetStore(this)
  }

  hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }

  _withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }
}

解析:

  • install(window.Vue)若是是Vue2,經過mixins的方式添加beforeCreate鉤子函數,把store傳給任何繼承這個Vue的實例(組件),因此全部組件都擁有一個$store的屬性。也即每一個組件都能拿到store。

  • 第二個斷言判斷是確保Vue在當前環境中,且須要Promise,強制Store做爲構造函數。

  • 可配置項有兩個值plugins和是否使用strict嚴格模式(只能經過commit改變狀態),plugins通常用於開發調試

  • 將全局(非模塊內)的dispatch和committhis綁定到store

Store類的靜態屬性

  • _committing:記錄當前是否commit中
  • _actions:記錄全部action的字段,有模塊做用域的用'/'分隔
  • _actionSubscribers:全局的dispatch後遍歷調用數組中的函數
  • _mutations:和actions同樣
  • _wrappedGetters:無論是使用了模塊仍是全局的getter都存在於此
  • _modules:全局模塊
  • _modulesNamespaceMap:全部模塊全存於此,可經過namespace取出想要的模塊
  • _subscribers:全局的commit調用以後,遍歷調用數組中的函數
  • _watcherVM:Vue實例用於watch()函數
  • strict:是否使用嚴格模式,使用那麼改變狀態只能經過commit來改變狀態
  • state:Store的全部state包含模塊的
  • _vm:Vue的實例,VueX真正狀態是存於這裏Store._vm._data.$$state

installModule

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

  // register in namespace map
  if (module.namespaced) {
    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)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  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)
  })
}
  • 這個函數主要做用是安裝模塊,在初始化根模塊的同時註冊全部子模塊,以及將全部getter(包括模塊中的)收集到this._wrappedGetters中。
  • _modulesNamespaceMap存放全部模塊,能夠經過namespaced來獲取模塊(數據結構:哈希表)
  • Vue.set(parentState, moduleName, module.state)因爲VueX是Vue的實例,Vue設置的狀態,它的實例(VueX)能夠繼承
  • 建立每一個模塊的context

makeLocalContext

/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 */
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''

  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 and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}
  • 這個函數主要建立局部的dispatch、commit、getters、state。存於context
  • 局部的意思是隻取當前做用域模塊的getters、state以及dispatch和commit,沒有做用域就取全局的
  • get和state採用數據劫持和懶獲取的方式
  • 懶:就是一個函數() => store.getters,只有調用這個函數才獲取到全部getters,結合get就是隻有引用這個屬性纔會獲取全部getters
  • 懶獲取就是把本來的操做封裝成函數,在須要的時候調用該函數便可得到。實際上就是宏命令或者叫命令模式,用一個函數把一塊要執行的命令封裝起來。
  • () => fn()要留意的是:是fn()而不是fnfn()纔是要執行的命令

makeLocalGetters

function makeLocalGetters (store, namespace) {
  const gettersProxy = {}

  const splitPos = namespace.length
  Object.keys(store.getters).forEach(type => {
    // skip if the target getter is not match this namespace
    if (type.slice(0, splitPos) !== namespace) return

    // extract local getter type
    const localType = type.slice(splitPos)

    // Add a port to the getters proxy.
    // Define as getter property because
    // we do not want to evaluate the getters in this time.
    Object.defineProperty(gettersProxy, localType, {
      get: () => store.getters[type],
      enumerable: true
    })
  })

  return gettersProxy
}
  • 先判斷有沒有匹配的做用域(getter = namespace + getterName),而後取出getter的名稱
  • 經過代理的方式返回這個getter

resetStoreVM

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

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    computed[key] = () => fn(store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  const silent = Vue.config.silent
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}
  • 主要是從新設置store._vm的值
  • _vm的$$state保存的是store.state
  • _vmcomputedstore._wrappedGetters的值
  • 能夠看出VueX的狀態是存在於一個Vue實例的$data中的
  • (數據劫持+懶調用)和computed就是getter的實現原理,也是爲何getter有緩存的效果
  • 這個computed是VueX內部的_vm的,也能夠認爲就是getters

registerMutation

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}
  • 主要是按命名空間劃分和收集不一樣命名空間的mutaion,以及把它們的this綁定爲store
  • local.state若是有命名空間使用命名空間的,沒有使用全局的
  • store._mutations[type],type就是命名空間,按命名空間來存放mutations

registerAction

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload, cb)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}
  • store._actions[type]存儲結構和_mutations的同樣,按命名空間來存儲action
  • 一樣綁定this爲store,可是第一個參數傳入了一個對象{ dispatch, commit, getters, state, rootGetters, rootState }
  • { dispatch, commit, getters, state }是局部local的
  • 運行後的結果是返回一個Promise

registerGetter

function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}
  • store._wrappedGetters[type]因爲全部getters放在一個對象,結構和actions、mutations的結構就不同了,就是一個對象
  • 鍵值仍是根據命名空間來生成
  • 傳入四個參數(local.state, local.getters, store.state, store.getters);有命名空間,前面2個參數就是命名空間的,沒有命名空間前面2個參數就是全局的

resetStore

function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}
  • 這個會從新建立Store,總體的,總體替換舊的Store
  • 從新installModuleresetStoreVM

class Store 的方法

state()

get state () {
    return this._vm._data.$$state
  }

  set state (v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `use store.replaceState() to explicit replace store state.`)
    }
  }
  • 獲取的state是直接從store._vm._data.$$state中獲取
  • 不能夠直接設置state

replaceState

replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }
  • 替換狀態是直接替換store._vm._data.$$state

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
    }
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    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'
      )
    }
  }
  • 全局的commit,因爲_mutations存儲的是全部的type(包括模塊的),這個commit能夠commit('shop/card')只要命名路徑對
  • 調用完後,會發布_subscribers,遍歷該數組調用回調

dispatch

dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    this._actionSubscribers.forEach(sub => sub(action, this.state))

    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }
  • 全局dispatch,和commit同樣,_actions存放的是全部的action的type(包括模塊的)
  • 調用完後會發佈_actionSubscribers中訂閱的回調函數
  • _actions[type]是一個數組,能夠一個type不一樣處理,也即_actions[type] = [fn1, fn2, fn3, ...]
  • 若是出現一個type多個處理,就用Promise.all等到全部函數都調用完才統一處理(支持異步)

subscribe

subscribe (fn) {
    return genericSubscribe(fn, this._subscribers)
  }
  • 訂閱commit,只要調用全局的commit就調用,模塊內的context.commit也是會調用所有的commit

subscribeAction

subscribeAction (fn) {
    return genericSubscribe(fn, this._actionSubscribers)
  }
  • 訂閱action,只要調用全局的action就調用,模塊內的context.dispatch也是會調用所有的dispatch

watch

watch (getter, cb, options) {
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }
  • 借用Vue$watch,觀察想要監控的狀態
  • 用這個函數就至關於建立一個getter,不一樣時可動態建立

動態模塊

  • registerModule
  • unregisterModule
  • hotUpdate

registerModule

registerModule (path, rawModule, options = {}) {
    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.`)
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }
  • 動態註冊模塊
  • 註冊一個模塊會從新建立Storestore._vm
  • installModuleresetStoreVM這兩個函數很總要,涉及到性能,重點、重點

unregisterModule

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])
    })
    resetStore(this)
  }
  • 註銷模塊
  • Vue.delete(parentState, path[path.length - 1])resetStore(this)
  • 仍是會從新建立Store_vm

hotUpdate

hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }
  • 更新模塊
  • resetStore(this, true),仍是會從新建立Store_vm

很重要的3個函數

  • resetStore:包含resetStoreVMinstallModule
  • resetStoreVM
  • installModule
相關文章
相關標籤/搜索