【vue-進(jìn)階】之深入理解Vuex

為什么需要Vuex

通常 Vue 項(xiàng)目中的數(shù)據(jù)通信雇毫,我們通過以下三種方式就可以解決,但是隨著項(xiàng)目多層嵌套的組件增加踩蔚,兄弟組件間的狀態(tài)傳遞非常繁瑣棚放,導(dǎo)致不斷的通過事件來變更狀態(tài),同步狀態(tài)多份拷貝寂纪,最后代碼難以維護(hù)席吴。于是尤大大開發(fā)了 Vuex 來解決這個(gè)問題赌结。

  • 父?jìng)髯?props
  • 子傳父 $emit孝冒;
  • eventBus 事件總線柬姚。

當(dāng)然中小 Vue 項(xiàng)目可以不使用 Vuex,當(dāng)出現(xiàn)下面這兩種情況的時(shí)候我們就應(yīng)該考慮使用 Vuex 統(tǒng)一管理狀態(tài)了庄涡。

  • 多個(gè)視圖依賴于同一狀態(tài)量承;
  • 來自不同視圖的行為需要變更同一狀態(tài)。

使用Vuex的優(yōu)點(diǎn)也很明顯:

  • 方便全局通信穴店;
  • 方便狀態(tài)緩存撕捍;
  • 方便通過 vue-devtools 來進(jìn)行狀態(tài)相關(guān)的bug排查。

Vuex初使用

官方 Vuex 上有一張用于解釋 Vuex 的圖泣洞,但是并沒有給于清晰明確的注釋忧风。這里簡(jiǎn)單說下每塊的功能和作用,以及整個(gè)流程圖的單向數(shù)據(jù)量的流向球凰。

Vuex
  • Vue Components:Vue組件狮腿。HTML頁面上,負(fù)責(zé)接收用戶操作等交互行為呕诉,執(zhí)行 dispatch 方法觸發(fā)對(duì)應(yīng) action 進(jìn)行回應(yīng)缘厢。

  • dispatch:操作行為觸發(fā)方法,是唯一能執(zhí)行action的方法甩挫。

  • actions:操作行為處理模塊贴硫。負(fù)責(zé)處理Vue Components接收到的所有交互行為。包含同步/異步操作伊者,支持多個(gè)同名方法英遭,按照注冊(cè)的順序依次觸發(fā)。向后臺(tái)API請(qǐng)求的操作就在這個(gè)模塊中進(jìn)行亦渗,包括觸發(fā)其他 action 以及提交 mutation 的操作贪绘。該模塊提供了Promise的封裝,以支持action的鏈?zhǔn)接|發(fā)央碟。

  • commit:狀態(tài)改變提交操作方法税灌。對(duì) mutation 進(jìn)行提交,是唯一能執(zhí)行mutation的方法亿虽。

  • mutations:狀態(tài)改變操作方法菱涤。是Vuex修改state的唯一推薦方法,其他修改方式在嚴(yán)格模式下將會(huì)報(bào)錯(cuò)洛勉。該方法只能進(jìn)行同步操作粘秆,且方法名只能全局唯一。操作之中會(huì)有一些hook暴露出來收毫,以進(jìn)行state的監(jiān)控等攻走。

  • state:頁面狀態(tài)管理容器對(duì)象殷勘。集中存儲(chǔ) Vue componentsdata對(duì)象的零散數(shù)據(jù),全局唯一昔搂,以進(jìn)行統(tǒng)一的狀態(tài)管理玲销。頁面顯示所需的數(shù)據(jù)從該對(duì)象中進(jìn)行讀取,利用Vue的細(xì)粒度數(shù)據(jù)響應(yīng)機(jī)制來進(jìn)行高效的狀態(tài)更新摘符。

  • Vue組件接收交互行為贤斜,調(diào)用 dispatch 方法觸發(fā) action 相關(guān)處理,若頁面狀態(tài)需要改變逛裤,則調(diào)用 commit 方法提交 mutation 修改 state瘩绒,通過 getters 獲取到 state 新值,重新渲染 Vue Components带族,界面隨之更新锁荔。

總結(jié):

  1. state里面就是存放的我們上面所提到的狀態(tài)。

  2. mutations就是存放如何更改狀態(tài)蝙砌。

  3. getters 就是從 state 中派生出狀態(tài)堕战,比如將 state 中的某個(gè)狀態(tài)進(jìn)行過濾然后獲取新的狀態(tài)。

  4. actions 就是 mutation 的加強(qiáng)版拍霜,它可以通過 commit mutations中的方法來改變狀態(tài),最重要的是它可以進(jìn)行異步操作薪介。

  5. modules 顧名思義祠饺,就是當(dāng)用這個(gè)容器來裝這些狀態(tài)還是顯得混亂的時(shí)候,我們就可以把容器分成幾塊汁政,把狀態(tài)和管理規(guī)則分類來裝道偷。這和我們創(chuàng)建js模塊是一個(gè)目的,讓代碼結(jié)構(gòu)更清晰记劈。

關(guān)于Vuex的疑問

我們做的項(xiàng)目中使用Vuex勺鸦,在使用Vuex的過程中留下了一些疑問,發(fā)現(xiàn)在使用層面并不能解答我的疑惑目木。于是將疑問簡(jiǎn)單羅列换途,最近在看了 Vuex 源碼才明白。

閱讀Vuex源碼的倉(cāng)庫(kù)

image.png
  • 如何保證 state 的修改只能在 mutation 的回調(diào)函數(shù)中刽射?
  • mutations 里的方法军拟,為什么可以修改 state
  • 為什么可以通過 this.commit 來調(diào)用 mutation 函數(shù)誓禁?
  • actions 函數(shù)中context對(duì)象懈息,為什么不是 store實(shí)例 本身?
  • 為什么在actions函數(shù)里可以調(diào)用 dispatch 或者 commit摹恰?
  • 通過 this.$store.getters.xx辫继,是如何可以訪問到 getter 函數(shù)的執(zhí)行結(jié)果的怒见?

Vuex源碼分析

針對(duì)以上疑問,在看Vuex源碼的過程中慢慢解惑了姑宽。

1. 如何保證 state 的修改只能在 mutation 的回調(diào)函數(shù)中遣耍?

Vuex源碼的 Store 類中有個(gè) _withCommit 函數(shù):

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

Vuex 中所有對(duì) state 的修改都會(huì)調(diào)用 _withCommit函數(shù)的包裝,保證在同步修改 state 的過程中 this._committing 的值始終為 true低千。當(dāng)我們檢測(cè)到 state 變化的時(shí)候配阵,如果 this._committing不為 true,則能查到這個(gè)狀態(tài)修改有問題示血。

2. mutations里的方法棋傍,為什么可以修改state?

Vuex實(shí)例化的時(shí)候难审,會(huì)調(diào)用 Store 瘫拣,Store 會(huì)調(diào)用 installModule,來對(duì)傳入的配置進(jìn)行模塊的注冊(cè)和安裝告喊。對(duì) mutations 進(jìn)行注冊(cè)和安裝麸拄,調(diào)用了 registerMutation 方法:

/**
 * 注冊(cè)mutation 作用同步修改當(dāng)前模塊的 state
 * @param {*} store  Store實(shí)例
 * @param {*} type  mutation 的 key
 * @param {*} handler  mutation 執(zhí)行的函數(shù)
 * @param {*} local  當(dāng)前模塊
 */
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)
  })
}

該方法對(duì)mutation方法進(jìn)行再次封裝,注意 handler.call(store, local.state, payload)黔姜,這里改變 mutation 執(zhí)行的函數(shù)的 this 指向?yàn)?Store實(shí)例拢切,local.state 為當(dāng)前模塊的 statepayload 為額外參數(shù)秆吵。

因?yàn)楦淖兞?mutation 執(zhí)行的函數(shù)的 this 指向?yàn)?Store實(shí)例淮椰,就方便對(duì) this.state 進(jìn)行修改。

3. 為什么可以通過 this.commit 來調(diào)用 mutation 函數(shù)纳寂?

在 Vuex 中主穗,mutation 的調(diào)用是通過 store 實(shí)例的 API 接口 commit 來調(diào)用的。來看一下 commit 函數(shù)的定義:

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

    const mutation = { type, payload }

    // 根據(jù) type 去查找對(duì)應(yīng)的 mutation
    const entry = this._mutations[type]
    // 沒查到 報(bào)錯(cuò)提示
    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毙芜,調(diào)用回調(diào)函數(shù)忽媒,并把 mutation 和當(dāng)前的根 state 作為參數(shù)傳入
    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的類型和外部參數(shù),在 commmit 的實(shí)現(xiàn)中通過 this._mutations[type] 去匹配到對(duì)應(yīng)的 mutation 函數(shù)腋粥,然后調(diào)用晦雨。

4. actions函數(shù)中context對(duì)象,為什么不是store實(shí)例本身隘冲?

5. 為什么在actions函數(shù)里可以調(diào)用 dispatch 或者 commit金赦?

actions的使用:

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

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

/**
 * 注冊(cè)actions
 * @param {*} store 全局store
 * @param {*} type action 類型
 * @param {*} handler action 函數(shù)
 * @param {*} local 當(dāng)前的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的時(shí)候執(zhí)行 賦值的
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

很明顯context對(duì)象是指定的,并不是store實(shí)例对嚼, const {dispatch, commit, getters, state, rootGetters,rootState } = context

context對(duì)象上掛載了:

  • dispatch, 當(dāng)前模塊上的dispatch函數(shù)
  • commit, 當(dāng)前模塊上的commit函數(shù)
  • getters, 當(dāng)前模塊上的getters
  • state, 當(dāng)前模塊上的state
  • rootGetters, 根模塊上的getters
  • rootState 根模塊上的state

6. 通過 this.$store.getters.xx夹抗,是如何可以訪問到getter函數(shù)的執(zhí)行結(jié)果的?

在Vuex源碼的Store實(shí)例的實(shí)現(xiàn)中有這樣一個(gè)方法 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 對(duì)象纵竖,在遍歷過程中拿到每個(gè) getter 的包裝函數(shù)漠烧,并把這個(gè)包裝函數(shù)執(zhí)行的結(jié)果用 computed 臨時(shí)保存杏愤。

然后實(shí)例化了一個(gè) Vue實(shí)例,把上面的 computed 作為計(jì)算屬性傳入已脓,把 狀態(tài)樹state 作為 data 傳入珊楼,這樣就完成了注冊(cè)。

我們就可以在組件中訪問 this.$store.getters.xxgetter了度液,相當(dāng)于訪問了 store._vm[xxgetter]厕宗,也就是在訪問 computed[xxgetter],這樣就訪問到 xxgetter 的回調(diào)函數(shù)了堕担。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末已慢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子霹购,更是在濱河造成了極大的恐慌佑惠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐疙,死亡現(xiàn)場(chǎng)離奇詭異膜楷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贞奋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門赌厅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人轿塔,你說我怎么就攤上這事特愿。” “怎么了催训?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宗收。 經(jīng)常有香客問我漫拭,道長(zhǎng),這世上最難降的妖魔是什么混稽? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任采驻,我火速辦了婚禮,結(jié)果婚禮上匈勋,老公的妹妹穿的比我還像新娘礼旅。我一直安慰自己,他們只是感情好洽洁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布痘系。 她就那樣靜靜地躺著,像睡著了一般饿自。 火紅的嫁衣襯著肌膚如雪汰翠。 梳的紋絲不亂的頭發(fā)上龄坪,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音复唤,去河邊找鬼健田。 笑死,一個(gè)胖子當(dāng)著我的面吹牛佛纫,可吹牛的內(nèi)容都是我干的妓局。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呈宇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼好爬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起攒盈,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤抵拘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后型豁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僵蛛,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年迎变,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了充尉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衣形,死狀恐怖驼侠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谆吴,我是刑警寧澤倒源,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站句狼,受9級(jí)特大地震影響笋熬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腻菇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一胳螟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筹吐,春花似錦糖耸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春周拐,著一層夾襖步出監(jiān)牢的瞬間铡俐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工妥粟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留审丘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓勾给,卻偏偏與公主長(zhǎng)得像滩报,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子播急,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 青玉案·元夕 宋.辛棄疾 東風(fēng)夜放花千樹脓钾, 更吹落,星如雨桩警。 寶馬雕車香滿路可训。 鳳簫聲動(dòng),玉壺光轉(zhuǎn)捶枢, 一夜魚龍舞握截。...
    Isophie閱讀 133評(píng)論 0 0
  • 父親推開門谨胞,看見我站在院子里。 驚訝的說你怎么來了蒜鸡,我說“高興吧胯努,你兒子大老遠(yuǎn)來看你》攴溃” 父親住的地方叶沛,像我見過的...
    阿焦文字閱讀 342評(píng)論 4 3
  • “你必須培養(yǎng)一些愛好,不要遙遠(yuǎn)空洞的目標(biāo)而是實(shí)在甚至庸俗的吃喝拉撒忘朝。必須一覺醒來很清楚至少今天還能做什么灰署。去樓下最...
    輕舟ling閱讀 238評(píng)論 0 2
  • 當(dāng)語文聽寫的成績(jī)發(fā)下來時(shí),我的心抽了一下辜伟;當(dāng)打開作業(yè)本后氓侧,我的心抽了兩下脊另。第一次是因?yàn)榫o張导狡,而第二次是因?yàn)閭摹0?..
    雪月夜行者閱讀 131評(píng)論 0 1