VUE全家桶之vuex核心原理解析

暴走的snail發表於2019-03-20

前言

現在前端開發中vuejs是大部分前端工程師的首選,只要不是很小的專案都會引用vuex來進行狀態管理。最近新開展的專案比較複雜,使用vuex的地方也比較多。在此過程中也遇到了不少問題。如今有時間正好研究下vuex框架原始碼,深入瞭解下他的底層實現。

為什麼要使用vuex

在當前前端的spa模組化專案中不可避免的是某些變數需要在全域性範圍內引用,此時父子元件的傳值,子父元件間的傳值,兄弟元件間的傳值成了我們需要解決的問題。雖然vue中提供了props(父傳子)commit(子傳父)兄弟間也可以用localstorage和sessionstorage。但是這種方式在專案開發中帶來的問題比他解決的問題(難管理,難維護,程式碼複雜,安全性低)更多。vuex的誕生也是為了解決這些問題,從而大大提高我們vue專案的開發效率。(信不信由你=-=)我接觸vue專案的這兩年,雖然一直在用vuex(用的還算熟練),但是還沒有深入的去了解過他的架構原理,最近正好有空,就來深入學習下。

vuex的整體架構主體

VUE全家桶之vuex核心原理解析

原始碼解析(工作原理)

  1. 根state的儲存位置 當你在使用vuex時有沒有想過你在程式碼中設定的state被vuex如何處理了呢
const store = new Vuex.Store({ 
state: { count: 0 }, 
mutations: { increment (state) { 
      state.count++ 
} } })
複製程式碼

我們設定在跟state中的屬性會被vuex儲存在根元素中

 this._modules = new ModuleCollection(options)//初始化
 const state = this._modules.root.state//獲取定義的state
複製程式碼

vuex初始化時先去獲取定義在state屬性中的值new ModuleCollection(options)進行模組收集(重新組裝我們定義在store中的相關屬性): 最終形成一棵module樹

VUE全家桶之vuex核心原理解析

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

  get (path) {
    return path.reduce((module, key) => {
      return module.getChild(key)
    }, this.root)
  }

  getNamespace (path) {
    let module = this.root
    return path.reduce((namespace, key) => {
      module = module.getChild(key)
      return namespace + (module.namespaced ? key + '/' : '')
    }, '')
  }

  update (rawRootModule) {
    update([], this.root, rawRootModule)
  }

  register (path, rawModule, runtime = true) {
    if (process.env.NODE_ENV !== 'production') {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

  unregister (path) {
    const parent = this.get(path.slice(0, -1))
    const key = path[path.length - 1]
    if (!parent.getChild(key).runtime) return

    parent.removeChild(key)
  }
}
複製程式碼
  1. commit(=>mutations)時做了什麼

const { dispatch, commit } = this //初始化先繫結commit

this.commit = function boundCommit(type, payload, options) {
      return commit.call(store, type, payload, options)
    }
複製程式碼
   繫結之後註冊mutation(commit的屬性) 
複製程式碼
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)
  })
}
複製程式碼
  commit(_type, _payload, _options) {
    console.log('================commit=====================')
    console.log(_type)
    console.log(_payload)
    console.log(_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
    }
    //在執行mutation的時候,會將_committing設定為true,執行完畢後重置,在開啟strict模式時,會監聽state的變化,當變化時_committing不為true時會給出警告
    this._withCommit(() => {
      //迭代傳入的commit陣列
      entry.forEach(function commitIterator(handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))
    //當開發環境是丟擲警告(如果commit的屬性不存在)
    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'
      )
    }
  }
複製程式碼

3.dispatch(=>actions)做了啥(初始化同commit)

   //dispatch定義位置
    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload)
    }
複製程式碼

繫結dispatch

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)
    //將res函式轉為非同步 promise
    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
    }
  })
}
複製程式碼

註冊actions

  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
    }

    try {
      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)
      }
    }

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

    return result.then(res => {
      try {
        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)
        }
      }
      return res
    })
  }
複製程式碼

其他

vuex和全域性變數的區別:(借用部落格)

1,【響應式】vuex的狀態儲存是響應式的,當Vue元件從store中讀取狀態的時候,若store中的狀態發生變化,那麼相應的元件也會得到高效更新。

2,【不能直接改變store】不能直接改變store的變化,改變store中狀態的唯一途徑是commit mutation。方便於跟蹤每一個狀態的變化。

相關文章