【vue-進階】之深刻學習Vuex

爲何須要Vuex

一般 Vue 項目中的數據通訊,咱們經過如下三種方式就能夠解決,可是隨着項目多層嵌套的組件增長,兄弟組件間的狀態傳遞很是繁瑣,致使不斷的經過事件來變動狀態,同步狀態多份拷貝,最後代碼難以維護。因而尤大大開發了 Vuex 來解決這個問題。javascript

  • 父傳子 props
  • 子傳父 $emit
  • eventBus 事件總線。

固然中小 Vue 項目能夠不使用 Vuex,當出現下面這兩種狀況的時候咱們就應該考慮使用 Vuex 統一管理狀態了。html

  • 多個視圖依賴於同一狀態;
  • 來自不一樣視圖的行爲須要變動同一狀態。

使用Vuex的優勢也很明顯:vue

  • 方便全局通訊;
  • 方便狀態緩存;
  • 方便經過 vue-devtools 來進行狀態相關的bug排查。

Vuex初使用

官方 Vuex 上有一張用於解釋 Vuex 的圖,可是並無給於清晰明確的註釋。這裏簡單說下每塊的功能和做用,以及整個流程圖的單向數據量的流向。java

Vuex

  • Vue Components:Vue組件。HTML頁面上,負責接收用戶操做等交互行爲,執行 dispatch 方法觸發對應 action 進行迴應。vuex

  • dispatch:操做行爲觸發方法,是惟一能執行action的方法。緩存

  • actions:操做行爲處理模塊。負責處理Vue Components接收到的全部交互行爲。包含同步/異步操做,支持多個同名方法,按照註冊的順序依次觸發。向後臺API請求的操做就在這個模塊中進行,包括觸發其餘 action 以及提交 mutation 的操做。該模塊提供了Promise的封裝,以支持action的鏈式觸發。架構

  • commit:狀態改變提交操做方法。對 mutation 進行提交,是惟一能執行mutation的方法。app

  • mutations:狀態改變操做方法。是Vuex修改state的惟一推薦方法,其餘修改方式在嚴格模式下將會報錯。該方法只能進行同步操做,且方法名只能全局惟一。操做之中會有一些hook暴露出來,以進行state的監控等。異步

  • state:頁面狀態管理容器對象。集中存儲 Vue componentsdata對象的零散數據,全局惟一,以進行統一的狀態管理。頁面顯示所需的數據從該對象中進行讀取,利用Vue的細粒度數據響應機制來進行高效的狀態更新。函數

  • Vue組件接收交互行爲,調用 dispatch 方法觸發 action 相關處理,若頁面狀態須要改變,則調用 commit 方法提交 mutation 修改 state,經過 getters 獲取到 state 新值,從新渲染 Vue Components,界面隨之更新。

總結:

  1. state裏面就是存放的咱們上面所提到的狀態。

  2. mutations就是存放如何更改狀態。

  3. getters 就是從 state 中派生出狀態,好比將 state 中的某個狀態進行過濾而後獲取新的狀態。

  4. actions 就是 mutation 的增強版,它能夠經過 commit mutations中的方法來改變狀態,最重要的是它能夠進行異步操做。

  5. modules 顧名思義,就是當用這個容器來裝這些狀態仍是顯得混亂的時候,咱們就能夠把容器分紅幾塊,把狀態和管理規則分類來裝。這和咱們建立js模塊是一個目的,讓代碼結構更清晰。

關於Vuex的疑問

咱們作的項目中使用Vuex,在使用Vuex的過程當中留下了一些疑問,發如今使用層面並不能解答個人疑惑。因而將疑問簡單羅列,最近在看了 Vuex 源碼才明白。

image.png

  • 如何保證 state 的修改只能在 mutation 的回調函數中?
  • mutations 裏的方法,爲何能夠修改 state
  • 爲何能夠經過 this.commit 來調用 mutation 函數?
  • actions 函數中context對象,爲何不是 store實例 自己?
  • 爲何在actions函數裏能夠調用 dispatch 或者 commit
  • 經過 this.$store.getters.xx,是如何能夠訪問到 getter 函數的執行結果的?

Vuex源碼分析

針對以上疑問,在看Vuex源碼的過程當中慢慢解惑了。

1. 如何保證 state 的修改只能在 mutation 的回調函數中?

Vuex源碼的 Store 類中有個 _withCommit 函數:

_withCommit (fn) {
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
}
複製代碼

Vuex 中全部對 state 的修改都會調用 _withCommit函數的包裝,保證在同步修改 state 的過程當中 this._committing 的值始終爲 true。當咱們檢測到 state 變化的時候,若是 this._committing不爲 true,則能查到這個狀態修改有問題。

2. mutations裏的方法,爲何能夠修改state?

Vuex實例化的時候,會調用 StoreStore 會調用 installModule,來對傳入的配置進行模塊的註冊和安裝。對 mutations 進行註冊和安裝,調用了 registerMutation 方法:

/** * 註冊mutation 做用同步修改當前模塊的 state * @param {*} store Store實例 * @param {*} type mutation 的 key * @param {*} handler mutation 執行的函數 * @param {*} local 當前模塊 */
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)
  })
}
複製代碼

該方法對mutation方法進行再次封裝,注意 handler.call(store, local.state, payload),這裏改變 mutation 執行的函數的 this 指向爲 Store實例local.state 爲當前模塊的 statepayload 爲額外參數。

由於改變了 mutation 執行的函數的 this 指向爲 Store實例,就方便對 this.state 進行修改。

3. 爲何能夠經過 this.commit 來調用 mutation 函數?

在 Vuex 中,mutation 的調用是經過 store 實例的 API 接口 commit 來調用的。來看一下 commit 函數的定義:

/** * * @param {*} _type mutation 的類型 * @param {*} _payload 額外的參數 * @param {*} _options 一些配置 */
  commit (_type, _payload, _options) {
    // check object-style commit
    // unifyObjectStyle 方法對 commit 多種形式傳參 進行處理
    // commit 的載荷形式和對象形式的底層處理
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options) 

    const mutation = { type, payload }

    // 根據 type 去查找對應的 mutation
    const entry = this._mutations[type]
    // 沒查到 報錯提示
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown mutation type: ${type}`)
      }
      return
    }

    // 使用了 this._withCommit 的方法提交 mutation
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    // 遍歷 this._subscribers,調用回調函數,並把 mutation 和當前的根 state 做爲參數傳入
    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'
      )
    }
}
複製代碼

this.commmit() 接收mutation的類型和外部參數,在 commmit 的實現中經過 this._mutations[type] 去匹配到對應的 mutation 函數,而後調用。

4. actions函數中context對象,爲何不是store實例自己?

5. 爲何在actions函數裏能夠調用 dispatch 或者 commit

actions的使用:

actions: {
    getTree(context) {
        getDepTree().then(res => {
            context.commit('updateTree', res.data)
        })
    }
}
複製代碼

在action的初始化函數中有這樣一段代碼:

/** * 註冊actions * @param {*} store 全局store * @param {*} type action 類型 * @param {*} handler action 函數 * @param {*} local 當前的module */
function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {

    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    // store._devtoolHook 是在store constructor的時候執行 賦值的
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

複製代碼

很明顯context對象是指定的,並非store實例, const {dispatch, commit, getters, state, rootGetters,rootState } = context

context對象上掛載了:

  • dispatch, 當前模塊上的dispatch函數
  • commit, 當前模塊上的commit函數
  • getters, 當前模塊上的getters
  • state, 當前模塊上的state
  • rootGetters, 根模塊上的getters
  • rootState 根模塊上的state

6. 經過 this.$store.getters.xx,是如何能夠訪問到getter函數的執行結果的?

在Vuex源碼的Store實例的實現中有這樣一個方法 resetStoreVM:

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

    // bind store public getters
    store.getters = {}
    const wrappedGetters = store._wrappedGetters
    const computed = {}
    Object.keys(wrappedGetters).forEach(key => {
        const fn = wrappedGetters[key]
        // use computed to leverage its lazy-caching mechanism
        computed[key] = () => fn(store)
        Object.defineProperty(store.getters, key, {
        get: () => store._vm[key]
        })
    })
    
    // ...
    
    store._vm = new Vue({
        data: { state },
        computed
    })
    
    // ...
}
複製代碼

遍歷 store._wrappedGetters 對象,在遍歷過程當中拿到每一個 getter 的包裝函數,並把這個包裝函數執行的結果用 computed 臨時保存。

而後實例化了一個 Vue實例,把上面的 computed 做爲計算屬性傳入,把 狀態樹state 做爲 data 傳入,這樣就完成了註冊。

咱們就能夠在組件中訪問 this.$store.getters.xxgetter了,至關於訪問了 store._vm[xxgetter],也就是在訪問 computed[xxgetter],這樣就訪問到 xxgetter 的回調函數了。

參考

相關文章
相關標籤/搜索