Vuex源碼閱讀筆記

筆記中的Vue與Vuex版本爲1.0.21和0.6.2,須要閱讀者有使用Vue,Vuex,ES6的經驗。javascript

原由

俗話說得好,沒有平白無故的愛,也沒有平白無故的恨,更不會平白無故的去閱讀別人的源代碼。
之因此會去閱讀Vuex的源代碼,是由於在剛開始接觸Vuex時,就在官方文檔的Actions部分,看到這麼一句:html

// the simplest action
function increment (store) {
  store.dispatch('INCREMENT')
}

// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
  dispatch('INCREMENT', amount)
}

上面的Action還好說,能看懂,可是下面使用ES6寫法的Action是什麼鬼呀喂(摔!)
雖然知道有解構賦值,可是那個{ dispatch }又是從哪兒冒出來的呀喂!明明我在調用時,沒有傳這個參數呀!
以前由於趕項目進度,因此抱着能用就行的態度,也就沒管那麼多。現在有了空閒時間,必須好好鑽研一下呀。
而鑽研最好的方式,就是閱讀Vuex的源代碼。這樣就能弄清楚,那個{ dispatch }到底從哪兒冒出來的。前端

Vuex源代碼簡介

Vuex的源代碼量挺少的,加起來也才600行不到,可是其中大量使用了ES6的語法,且部分功能(如Vuex初始化)使用到了Vue。因此讀起來仍是有些費勁的。
整個Vuex的源代碼,核心內容包括兩部分。一部分是Store的構造函數,另外一部分則是Vuex的初始化函數。
而剛纔問題的答案,就在第二部分。vue

問題場景還原

首先要介紹的,就是Vuex在Vue項目中的初始化。這兒貼一段代碼:
首先是Vuex中,我寫的Actions源代碼:java

// global/Vuex/action.js
export const getMe = ({ dispatch }) => {
  /**
   * 異步操做,獲取用戶信息,並存入Vuex的state中
   */
  res.user.get_me()
  .then(data => {
    dispatch('GET_ME', data)
  })
  .catch(err => {
    console.log(err)
  })
}

這個則是頂層組件,調用store的地方。因爲Vuex的特色,store只須要在最頂層的組件聲明一次。vuex

<template>
  <div id="wrapper">
    <router-view></router-view>
  </div>
</template>

<script type="text/javascript">
  import store from './Vuex/store.js'

  export default {
    store
  }
</script>

接下來則是組件中,則是實際調用Vuex的代碼。api

// index.vue
import { getMe } from './../global/Vuex/action'

export default {

  vuex: {
    actions: {
      getMe
    },
    getters: {
      // 從state中獲取信息
      user: state => state.user
    }
  },

  ready() {
    // 開始獲取用戶信息
    this.getMe()
  }
}

在這兒,能夠很明顯的看出,我在使用this.getMe()時,是沒有任何參數的。可是在getMe函數的定義中,是須要解構賦值出{dispatch}的。
就比如說這個:閉包

function getX({ x }) {
  console.log(x)
}

getX({ x: 3, y: 5 })
// 3

你得傳入相應的參數,才能進行解構賦值。
同時,我注意到在Vuex的Actions調用,須要在Vue的options的Vuex.actions中先聲明,以後才能使用。
那麼,必定是Vuex對這個Action動了手腳。(逃)
而動手腳的代碼,就存在於Vuex源代碼的override.js中。這個文件,是用於初始化Vuex的。app

Vuex的初始化

override.js中,有個vuexInit的函數。看名字就知道,這是拿來初始化Vuex的。
在代碼開頭,有這麼一句:異步

const options = this.$options
const { store, vuex } = options
// 感受解構賦值真的很棒,這樣寫能省不少時間。
// 下面的是老寫法
// const store = options.store
// const vuex = options.vuex

在這兒,用因而在Vue中調用,因此this指向Vue,而this.$options則是Vue的配置項。
也就是寫Vue組件時的:
export default {……一些配置}
這裏,就把Vue配置項的store和vuex抽離出來了。

搜尋store

接下來,則看到了Vuex源代碼的精妙之處:

// store injection
if (store) {
  this.$store = store
} else if (options.parent && options.parent.$store) {
  this.$store = options.parent.$store
}

解構賦值並非必定成功的,若是store在options中不存在,那麼store就會是undefined。可是咱們須要找store。
因而Vuex提供了向父級(Vue中的功能)尋找store的功能。不難看出,這兒父級的$store若是不存在,那麼其實他也會到本身的父級去尋找。直到找到爲止。
就想一條鎖鏈同樣,一層一層的連到最頂部store。因此在沒有找到時,Vuex會給你報個錯誤。

// 聲明瞭Vuex但沒有找到store時的情況
if (vuex) {
  if (!this.$store) {
    console.warn(
      '[vuex] store not injected. make sure to ' +
      'provide the store option in your root component.'
    )
  }

對Vuex聲明的內容,進行改造

接下來,則是對Vuex聲明的內容,進行改造。
首先的是獲取Vuex對象的內容:

let { state, getters, actions } = vuex

同時,在這兒還看到了對過期API的處理。感受算是意料以外的驚喜。

// handle deprecated state option
// 若是使用state而不是getters來獲取Store的數據,則會提示你state已通過時的,你須要使用新的api。
// 可是,這兒也作了兼容,確保升級時服務不會掛掉。
if (state && !getters) {
  console.warn(
    '[vuex] vuex.state option will been deprecated in 1.0. ' +
    'Use vuex.getters instead.'
  )
  getters = state
}

接下來,則是對getters和actions的處理:

// getters
if (getters) {
  options.computed = options.computed || {}
  for (let key in getters) {
    defineVuexGetter(this, key, getters[key])
  }
}
// actions
if (actions) {
  options.methods = options.methods || {}
  for (let key in actions) {
    options.methods[key] = makeBoundAction(this.$store, actions[key], key)
  }
}

能夠看出,在這兒對getters和actions都進行了額外處理。
在這兒,咱們講述actions的額外處理,至於getters,涉及了過多的Vue,而我不是很熟悉。等我多鑽研後,再寫吧。

Actions的改造

對整個Actions的改造,首先是Vuex的檢測:

// actions
if (actions) {
  // options.methods是Vue的methods選項
  options.methods = options.methods || {}
  for (let key in actions) {
    options.methods[key] = makeBoundAction(this.$store, actions[key], key)
  }
}

在這兒,咱們一點一點的剖析。能夠看出,全部的actions,都會被makeBoundAction函數處理,並加入Vue的methods選項中。
那麼看來,makeBoundAction函數就是我要找的答案了。
接下來貼出makeBoundAction函數的源代碼:

/**
 * Make a bound-to-store version of a raw action function.
 *
 * @param {Store} store
 * @param {Function} action
 * @param {String} key
 */

function makeBoundAction(store, action, key) {
  if (typeof action !== 'function') {
    console.warn(`[vuex] Action bound to key 'vuex.actions.${key}' is not a function.`)
  }
  return function vuexBoundAction(...args) {
    return action.call(this, store, ...args)
  }
}

事情到這兒,其實已經豁然明朗了。
我在Vuex中傳入的actions,實際會被處理爲vuexBoundAction,並加入options.methods中。
在調用這個函數時,實際上的action會使用call,來改變this指向並傳入store做爲第一個參數。而store是有dispatch這個函數的。
那麼,在我傳入{dispatch}時,天然而然就會解構賦值。
這樣的話,也造成了閉包,確保action能訪問到store。

結語

今天應該算是解決了心中的一個大疑惑,仍是那句話:

沒有平白無故的愛,也沒有平白無故的恨,更沒有平白無故冒出來的代碼。

整個源代碼讀下來一遍,雖然有些部分不太理解,可是對ES6和一些代碼的使用的理解又加深了一步。好比這回就鞏固了我關於ES6解構賦值的知識。並且還收穫了不少別的東西。總而言之,收穫頗豐~
最後的,依然是那句話:前端路漫漫,且行且歌。

最後附上本人博客地址和原文連接,但願能與各位多多交流。

Lxxyx的前端樂園
原文連接:Vuex源碼閱讀筆記

相關文章
相關標籤/搜索