国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Vuex源碼解析

chinafgj / 3455人閱讀

摘要:可能會(huì)有理解存在偏差的地方,歡迎提指出,共同學(xué)習(xí),共同進(jìn)步。先來(lái)看一下這張的數(shù)據(jù)流程圖,熟悉使用的同學(xué)應(yīng)該已經(jīng)有所了解。它允許用戶在某些情況下避免自動(dòng)安裝。

寫在前面

因?yàn)閷?duì)Vue.js很感興趣,而且平時(shí)工作的技術(shù)棧也是Vue.js,這幾個(gè)月花了些時(shí)間研究學(xué)習(xí)了一下Vue.js源碼,并做了總結(jié)與輸出。

文章的原地址:https://github.com/answershuto/learnVue。

在學(xué)習(xí)過(guò)程中,為Vue加上了中文的注釋https://github.com/answershuto/learnVue/tree/master/vue-src以及Vuex的注釋https://github.com/answershuto/learnVue/tree/master/vuex-src,希望可以對(duì)其他想學(xué)習(xí)源碼的小伙伴有所幫助。

可能會(huì)有理解存在偏差的地方,歡迎提issue指出,共同學(xué)習(xí),共同進(jìn)步。

Vuex

我們?cè)谑褂肰ue.js開發(fā)復(fù)雜的應(yīng)用時(shí),經(jīng)常會(huì)遇到多個(gè)組件共享同一個(gè)狀態(tài),亦或是多個(gè)組件會(huì)去更新同一個(gè)狀態(tài),在應(yīng)用代碼量較少的時(shí)候,我們可以組件間通信去維護(hù)修改數(shù)據(jù),或者是通過(guò)事件總線來(lái)進(jìn)行數(shù)據(jù)的傳遞以及修改。但是當(dāng)應(yīng)用逐漸龐大以后,代碼就會(huì)變得難以維護(hù),從父組件開始通過(guò)prop傳遞多層嵌套的數(shù)據(jù)由于層級(jí)過(guò)深而顯得異常脆弱,而事件總線也會(huì)因?yàn)榻M件的增多、代碼量的增大而顯得交互錯(cuò)綜復(fù)雜,難以捋清其中的傳遞關(guān)系。

那么為什么我們不能將數(shù)據(jù)層與組件層抽離開來(lái)呢?把數(shù)據(jù)層放到全局形成一個(gè)單一的Store,組件層變得更薄,專門用來(lái)進(jìn)行數(shù)據(jù)的展示及操作。所有數(shù)據(jù)的變更都需要經(jīng)過(guò)全局的Store來(lái)進(jìn)行,形成一個(gè)單向數(shù)據(jù)流,使數(shù)據(jù)變化變得“可預(yù)測(cè)”。

Vuex是一個(gè)專門為Vue.js框架設(shè)計(jì)的、用于對(duì)Vue.js應(yīng)用程序進(jìn)行狀態(tài)管理的庫(kù),它借鑒了Flux、redux的基本思想,將共享的數(shù)據(jù)抽離到全局,以一個(gè)單例存放,同時(shí)利用Vue.js的響應(yīng)式機(jī)制來(lái)進(jìn)行高效的狀態(tài)管理與更新。正是因?yàn)閂uex使用了Vue.js內(nèi)部的“響應(yīng)式機(jī)制”,所以Vuex是一個(gè)專門為Vue.js設(shè)計(jì)并與之高度契合的框架(優(yōu)點(diǎn)是更加簡(jiǎn)潔高效,缺點(diǎn)是只能跟Vue.js搭配使用)。具體使用方法及API可以參考Vuex的官網(wǎng)。

先來(lái)看一下這張Vuex的數(shù)據(jù)流程圖,熟悉Vuex使用的同學(xué)應(yīng)該已經(jīng)有所了解。

Vuex實(shí)現(xiàn)了一個(gè)單向數(shù)據(jù)流,在全局擁有一個(gè)State存放數(shù)據(jù),所有修改State的操作必須通過(guò)Mutation進(jìn)行,Mutation的同時(shí)提供了訂閱者模式供外部插件調(diào)用獲取State數(shù)據(jù)的更新。所有異步接口需要走Action,常見于調(diào)用后端接口異步獲取更新數(shù)據(jù),而Action也是無(wú)法直接修改State的,還是需要通過(guò)Mutation來(lái)修改State的數(shù)據(jù)。最后,根據(jù)State的變化,渲染到視圖上。Vuex運(yùn)行依賴Vue內(nèi)部數(shù)據(jù)雙向綁定機(jī)制,需要new一個(gè)Vue對(duì)象來(lái)實(shí)現(xiàn)“響應(yīng)式化”,所以Vuex是一個(gè)專門為Vue.js設(shè)計(jì)的狀態(tài)管理庫(kù)。

安裝

使用過(guò)Vuex的朋友一定知道,Vuex的安裝十分簡(jiǎn)單,只需要提供一個(gè)store,然后執(zhí)行下面兩句代碼即完成的Vuex的引入。

Vue.use(Vuex);

/*將store放入Vue創(chuàng)建時(shí)的option中*/
new Vue({
    el: "#app",
    store
});

那么問(wèn)題來(lái)了,Vuex是怎樣把store注入到Vue實(shí)例中去的呢?

Vue.js提供了Vue.use方法用來(lái)給Vue.js安裝插件,內(nèi)部通過(guò)調(diào)用插件的install方法(當(dāng)插件是一個(gè)對(duì)象的時(shí)候)來(lái)進(jìn)行插件的安裝。

我們來(lái)看一下Vuex的install實(shí)現(xiàn)。

/*暴露給外部的插件install方法,供Vue.use調(diào)用安裝插件*/
export function install (_Vue) {
  if (Vue) {
    /*避免重復(fù)安裝(Vue.use內(nèi)部也會(huì)檢測(cè)一次是否重復(fù)安裝同一個(gè)插件)*/
    if (process.env.NODE_ENV !== "production") {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      )
    }
    return
  }
  /*保存Vue,同時(shí)用于檢測(cè)是否重復(fù)安裝*/
  Vue = _Vue
  /*將vuexInit混淆進(jìn)Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
  applyMixin(Vue)
}

這段install代碼做了兩件事情,一件是防止Vuex被重復(fù)安裝,另一件是執(zhí)行applyMixin,目的是執(zhí)行vuexInit方法初始化Vuex。Vuex針對(duì)Vue1.0與2.0分別進(jìn)行了不同的處理,如果是Vue1.0,Vuex會(huì)將vuexInit方法放入Vue的_init方法中,而對(duì)于Vue2.0,則會(huì)將vuexinit混淆進(jìn)Vue的beforeCreacte鉤子中。來(lái)看一下vuexInit的代碼。

 /*Vuex的init鉤子,會(huì)存入每一個(gè)Vue實(shí)例等鉤子列表*/
  function vuexInit () {
    const options = this.$options
    // store injection
    if (options.store) {
      /*存在store其實(shí)代表的就是Root節(jié)點(diǎn),直接執(zhí)行store(function時(shí))或者使用store(非function)*/
      this.$store = typeof options.store === "function"
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      /*子組件直接從父組件中獲取$store,這樣就保證了所有組件都公用了全局的同一份store*/
      this.$store = options.parent.$store
    }
  }

vuexInit會(huì)嘗試從options中獲取store,如果當(dāng)前組件是根組件(Root節(jié)點(diǎn)),則options中會(huì)存在store,直接獲取賦值給$store即可。如果當(dāng)前組件非根組件,則通過(guò)options中的parent獲取父組件的$store引用。這樣一來(lái),所有的組件都獲取到了同一份內(nèi)存地址的Store實(shí)例,于是我們可以在每一個(gè)組件中通過(guò)this.$store愉快地訪問(wèn)全局的Store實(shí)例了。

那么,什么是Store實(shí)例?

Store

我們傳入到根組件到store,就是Store實(shí)例,用Vuex提供到Store方法構(gòu)造。

export default new Vuex.Store({
    strict: true,
    modules: {
        moduleA,
        moduleB
    }
});

我們來(lái)看一下Store的實(shí)現(xiàn)。首先是構(gòu)造函數(shù)。

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
    /*
      在瀏覽器環(huán)境下,如果插件還未安裝(!Vue即判斷是否未安裝),則它會(huì)自動(dòng)安裝。
      它允許用戶在某些情況下避免自動(dòng)安裝。
    */
    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 {
      /*一個(gè)數(shù)組,包含應(yīng)用在 store 上的插件方法。這些插件直接接收 store 作為唯一參數(shù),可以監(jiān)聽 mutation(用于外部地?cái)?shù)據(jù)持久化、記錄或調(diào)試)或者提交 mutation (用于內(nèi)部數(shù)據(jù),例如 websocket 或 某些觀察者)*/
      plugins = [],
      /*使 Vuex store 進(jìn)入嚴(yán)格模式,在嚴(yán)格模式下,任何 mutation 處理函數(shù)以外修改 Vuex state 都會(huì)拋出錯(cuò)誤。*/
      strict = false
    } = options

    /*從option中取出state,如果state是function則執(zhí)行,最終得到一個(gè)對(duì)象*/
    let {
      state = {}
    } = options
    if (typeof state === "function") {
      state = state()
    }

    // store internal state
    /* 用來(lái)判斷嚴(yán)格模式下是否是用mutation修改state的 */
    this._committing = false
    /* 存放action */
    this._actions = Object.create(null)
    /* 存放mutation */
    this._mutations = Object.create(null)
    /* 存放getter */
    this._wrappedGetters = Object.create(null)
    /* module收集器 */
    this._modules = new ModuleCollection(options)
    /* 根據(jù)namespace存放module */
    this._modulesNamespaceMap = Object.create(null)
    /* 存放訂閱者 */
    this._subscribers = []
    /* 用以實(shí)現(xiàn)Watch的Vue實(shí)例 */
    this._watcherVM = new Vue()

    // bind commit and dispatch to self
    /*將dispatch與commit調(diào)用的this綁定為store對(duì)象本身,否則在組件內(nèi)部this.dispatch時(shí)的this會(huì)指向組件的vm*/
    const store = this
    const { dispatch, commit } = this
    /* 為dispatch與commit綁定this(Store實(shí)例本身) */
    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
    /*嚴(yán)格模式(使 Vuex store 進(jìn)入嚴(yán)格模式,在嚴(yán)格模式下,任何 mutation 處理函數(shù)以外修改 Vuex state 都會(huì)拋出錯(cuò)誤)*/
    this.strict = strict

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    /*初始化根module,這也同時(shí)遞歸注冊(cè)了所有子modle,收集所有module的getter到_wrappedGetters中去,this._modules.root代表根module才獨(dú)有保存的Module對(duì)象*/
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    /* 通過(guò)vm重設(shè)store,新建Vue對(duì)象使用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊(cè)state以及computed */
    resetStoreVM(this, state)

    // apply plugins
    /* 調(diào)用插件 */
    plugins.forEach(plugin => plugin(this))

    /* devtool插件 */
    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

Store的構(gòu)造類除了初始化一些內(nèi)部變量以外,主要執(zhí)行了installModule(初始化module)以及resetStoreVM(通過(guò)VM使store“響應(yīng)式”)。

installModule

installModule的作用主要是用為module加上namespace名字空間(如果有)后,注冊(cè)mutation、action以及getter,同時(shí)遞歸安裝所有子module。

/*初始化module*/
function installModule (store, rootState, path, module, hot) {
  /* 是否是根module */
  const isRoot = !path.length
  /* 獲取module的namespace */
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  /* 如果有namespace則在_modulesNamespaceMap中注冊(cè) */
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    /* 獲取父級(jí)的state */
    const parentState = getNestedState(rootState, path.slice(0, -1))
    /* module的name */
    const moduleName = path[path.length - 1]
    store.`_withCommit`(() => {
      /* 將子module設(shè)置稱響應(yīng)式的 */
      Vue.set(parentState, moduleName, module.state)
    })
  }

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

  /* 遍歷注冊(cè)mutation */
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  /* 遍歷注冊(cè)action */
  module.forEachAction((action, key) => {
    const namespacedType = namespace + key
    registerAction(store, namespacedType, action, local)
  })

  /* 遍歷注冊(cè)getter */
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  /* 遞歸安裝mudule */
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}
resetStoreVM

在說(shuō)resetStoreVM之前,先來(lái)看一個(gè)小demo。

let globalData = {
    d: "hello world"
};
new Vue({
    data () {
        return {
            $$state: {
                globalData
            }
        }
    }
});

/* modify */
setTimeout(() => {
    globalData.d = "hi~";
}, 1000);

Vue.prototype.globalData = globalData;

/* 任意模板中 */
{{globalData.d}}

上述代碼在全局有一個(gè)globalData,它被傳入一個(gè)Vue對(duì)象的data中,之后在任意Vue模板中對(duì)該變量進(jìn)行展示,因?yàn)榇藭r(shí)globalData已經(jīng)在Vue的prototype上了所以直接通過(guò)this.prototype訪問(wèn),也就是在模板中的{{prototype.d}}。此時(shí),setTimeout在1s之后將globalData.d進(jìn)行修改,我們發(fā)現(xiàn)模板中的globalData.d發(fā)生了變化。其實(shí)上述部分就是Vuex依賴Vue核心實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”。

不熟悉Vue.js響應(yīng)式原理的同學(xué)可以通過(guò)筆者另一篇文章響應(yīng)式原理了解Vue.js是如何進(jìn)行數(shù)據(jù)雙向綁定的。

接著來(lái)看代碼。

/* 通過(guò)vm重設(shè)store,新建Vue對(duì)象使用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊(cè)state以及computed */
function resetStoreVM (store, state, hot) {
  /* 存放之前的vm對(duì)象 */
  const oldVm = store._vm 

  // bind store public getters
  store.getters = {}
  const wrappedGetters = store._wrappedGetters
  const computed = {}

  /* 通過(guò)Object.defineProperty為每一個(gè)getter方法設(shè)置get方法,比如獲取this.$store.getters.test的時(shí)候獲取的是store._vm.test,也就是Vue對(duì)象的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暫時(shí)設(shè)置為true的目的是在new一個(gè)Vue實(shí)例的過(guò)程中不會(huì)報(bào)出一切警告 */
  Vue.config.silent = true
  /*  這里new了一個(gè)Vue對(duì)象,運(yùn)用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊(cè)state以及computed*/
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  /* 使能嚴(yán)格模式,保證修改store只能通過(guò)mutation */
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    /* 解除舊vm的state的引用,以及銷毀舊的Vue對(duì)象 */
    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())
  }
}

resetStoreVM首先會(huì)遍歷wrappedGetters,使用Object.defineProperty方法為每一個(gè)getter綁定上get方法,這樣我們就可以在組件里訪問(wèn)this.$store.getter.test就等同于訪問(wèn)store._vm.test。

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

之后Vuex采用了new一個(gè)Vue對(duì)象來(lái)實(shí)現(xiàn)數(shù)據(jù)的“響應(yīng)式化”,運(yùn)用Vue.js內(nèi)部提供的數(shù)據(jù)雙向綁定功能來(lái)實(shí)現(xiàn)store的數(shù)據(jù)與視圖的同步更新。

store._vm = new Vue({
  data: {
    $$state: state
  },
  computed
})

這時(shí)候我們?cè)L問(wèn)store._vm.test也就訪問(wèn)了Vue實(shí)例中的屬性。

這兩步執(zhí)行完以后,我們就可以通過(guò)this.$store.getter.test訪問(wèn)vm中的test屬性了。

嚴(yán)格模式

Vuex的Store構(gòu)造類的option有一個(gè)strict的參數(shù),可以控制Vuex執(zhí)行嚴(yán)格模式,嚴(yán)格模式下,所有修改state的操作必須通過(guò)mutation實(shí)現(xiàn),否則會(huì)拋出錯(cuò)誤。

/* 使能嚴(yán)格模式 */
function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    if (process.env.NODE_ENV !== "production") {
      /* 檢測(cè)store中的_committing的值,如果是true代表不是通過(guò)mutation的方法修改的 */
      assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    }
  }, { deep: true, sync: true })
}

首先,在嚴(yán)格模式下,Vuex會(huì)利用vm的$watch方法來(lái)觀察$$state,也就是Store的state,在它被修改的時(shí)候進(jìn)入回調(diào)。我們發(fā)現(xiàn),回調(diào)中只有一句話,用assert斷言來(lái)檢測(cè)store._committing,當(dāng)store._committing為false的時(shí)候會(huì)觸發(fā)斷言,拋出異常。

我們發(fā)現(xiàn),Store的commit方法中,執(zhí)行mutation的語(yǔ)句是這樣的。

this._withCommit(() => {
  entry.forEach(function commitIterator (handler) {
    handler(payload)
  })
})

再來(lái)看看_withCommit的實(shí)現(xiàn)。

_withCommit (fn) {
  /* 調(diào)用withCommit修改state的值時(shí)會(huì)將store的committing值置為true,內(nèi)部會(huì)有斷言檢查該值,在嚴(yán)格模式下只允許使用mutation來(lái)修改store中的值,而不允許直接修改store的數(shù)值 */
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}

我們發(fā)現(xiàn),通過(guò)commit(mutation)修改state數(shù)據(jù)的時(shí)候,會(huì)再調(diào)用mutation方法之前將committing置為true,接下來(lái)再通過(guò)mutation函數(shù)修改state中的數(shù)據(jù),這時(shí)候觸發(fā)$watch中的回調(diào)斷言committing是不會(huì)拋出異常的(此時(shí)committing為true)。而當(dāng)我們直接修改state的數(shù)據(jù)時(shí),觸發(fā)$watch的回調(diào)執(zhí)行斷言,這時(shí)committing為false,則會(huì)拋出異常。這就是Vuex的嚴(yán)格模式的實(shí)現(xiàn)。

接下來(lái)我們來(lái)看看Store提供的一些API。

commit(mutation)
/* 調(diào)用mutation的commit方法 */
commit (_type, _payload, _options) {
  // check object-style commit
  /* 校驗(yàn)參數(shù) */
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  /* 取出type對(duì)應(yīng)的mutation的方法 */
  const entry = this._mutations[type]
  if (!entry) {
    if (process.env.NODE_ENV !== "production") {
      console.error(`[vuex] unknown mutation type: ${type}`)
    }
    return
  }
  /* 執(zhí)行mutation中的所有方法 */
  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方法會(huì)根據(jù)type找到并調(diào)用_mutations中的所有type對(duì)應(yīng)的mutation方法,所以當(dāng)沒(méi)有namespace的時(shí)候,commit方法會(huì)觸發(fā)所有module中的mutation方法。再執(zhí)行完所有的mutation之后會(huì)執(zhí)行_subscribers中的所有訂閱者。我們來(lái)看一下_subscribers是什么。

Store給外部提供了一個(gè)subscribe方法,用以注冊(cè)一個(gè)訂閱函數(shù),會(huì)push到Store實(shí)例的_subscribers中,同時(shí)返回一個(gè)從_subscribers中注銷該訂閱者的方法。

/* 注冊(cè)一個(gè)訂閱函數(shù),返回取消訂閱的函數(shù) */
subscribe (fn) {
  const subs = this._subscribers
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

在commit結(jié)束以后則會(huì)調(diào)用這些_subscribers中的訂閱者,這個(gè)訂閱者模式提供給外部一個(gè)監(jiān)視state變化的可能。state通過(guò)mutation改變時(shí),可以有效補(bǔ)獲這些變化。

dispatch(action)

來(lái)看一下dispatch的實(shí)現(xiàn)。

/* 調(diào)用action的dispatch方法 */
dispatch (_type, _payload) {
  // check object-style dispatch
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  /* actions中取出type對(duì)應(yīng)的ation */
  const entry = this._actions[type]
  if (!entry) {
    if (process.env.NODE_ENV !== "production") {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }

  /* 是數(shù)組則包裝Promise形成一個(gè)新的Promise,只有一個(gè)則直接返回第0個(gè) */
  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}

以及registerAction時(shí)候做的事情。

/* 遍歷注冊(cè)action */
function registerAction (store, type, handler, local) {
  /* 取出type對(duì)應(yīng)的action */
  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)
    /* 判斷是否是Promise */
    if (!isPromise(res)) {
      /* 不是Promise對(duì)象的時(shí)候轉(zhuǎn)化稱Promise對(duì)象 */
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      /* 存在devtool插件的時(shí)候觸發(fā)vuex的error給devtool */
      return res.catch(err => {
        store._devtoolHook.emit("vuex:error", err)
        throw err
      })
    } else {
      return res
    }
  })
}

因?yàn)閞egisterAction的時(shí)候?qū)ush進(jìn)_actions的action進(jìn)行了一層封裝(wrappedActionHandler),所以我們?cè)谶M(jìn)行dispatch的第一個(gè)參數(shù)中獲取state、commit等方法。之后,執(zhí)行結(jié)果res會(huì)被進(jìn)行判斷是否是Promise,不是則會(huì)進(jìn)行一層封裝,將其轉(zhuǎn)化成Promise對(duì)象。dispatch時(shí)則從_actions中取出,只有一個(gè)的時(shí)候直接返回,否則用Promise.all處理再返回。

watch
/* 觀察一個(gè)getter方法 */
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的朋友應(yīng)該很熟悉watch這個(gè)方法。這里采用了比較巧妙的設(shè)計(jì),_watcherVM是一個(gè)Vue的實(shí)例,所以watch就可以直接采用了Vue內(nèi)部的watch特性提供了一種觀察數(shù)據(jù)getter變動(dòng)的方法。

registerModule
/* 注冊(cè)一個(gè)動(dòng)態(tài)module,當(dāng)業(yè)務(wù)進(jìn)行異步加載的時(shí)候,可以通過(guò)該接口進(jìn)行注冊(cè)動(dòng)態(tài)module */
registerModule (path, rawModule) {
  /* 轉(zhuǎn)化稱Array */
  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.")
  }

  /*注冊(cè)*/
  this._modules.register(path, rawModule)
  /*初始化module*/
  installModule(this, this.state, path, this._modules.get(path))
  // reset store to update getters...
  /* 通過(guò)vm重設(shè)store,新建Vue對(duì)象使用Vue內(nèi)部的響應(yīng)式實(shí)現(xiàn)注冊(cè)state以及computed */
  resetStoreVM(this, this.state)
}

registerModule用以注冊(cè)一個(gè)動(dòng)態(tài)模塊,也就是在store創(chuàng)建以后再注冊(cè)模塊的時(shí)候用該接口。內(nèi)部實(shí)現(xiàn)實(shí)際上也只有installModule與resetStoreVM兩個(gè)步驟,前面已經(jīng)講過(guò),這里不再累述。

unregisterModule
 /* 注銷一個(gè)動(dòng)態(tài)module */
unregisterModule (path) {
  /* 轉(zhuǎn)化稱Array */
  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(() => {
    /* 獲取父級(jí)的state */
    const parentState = getNestedState(this.state, path.slice(0, -1))
    /* 從父級(jí)中刪除 */
    Vue.delete(parentState, path[path.length - 1])
  })
  /* 重制store */
  resetStore(this)
}

同樣,與registerModule對(duì)應(yīng)的方法unregisterModule,動(dòng)態(tài)注銷模塊。實(shí)現(xiàn)方法是先從state中刪除模塊,然后用resetStore來(lái)重制store。

resetStore
/* 重制store */
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)
}

這里的resetStore其實(shí)也就是將store中的_actions等進(jìn)行初始化以后,重新執(zhí)行installModule與resetStoreVM來(lái)初始化module以及用Vue特性使其“響應(yīng)式化”,這跟構(gòu)造函數(shù)中的是一致的。

插件

Vue提供了一個(gè)非常好用的插件Vue.js devtools

/* 從window對(duì)象的__VUE_DEVTOOLS_GLOBAL_HOOK__中獲取devtool插件 */
const devtoolHook =
  typeof window !== "undefined" &&
  window.__VUE_DEVTOOLS_GLOBAL_HOOK__

export default function devtoolPlugin (store) {
  if (!devtoolHook) return

  /* devtoll插件實(shí)例存儲(chǔ)在store的_devtoolHook上 */
  store._devtoolHook = devtoolHook

  /* 出發(fā)vuex的初始化事件,并將store的引用地址傳給deltool插件,使插件獲取store的實(shí)例 */
  devtoolHook.emit("vuex:init", store)

  /* 監(jiān)聽travel-to-state事件 */
  devtoolHook.on("vuex:travel-to-state", targetState => {
    /* 重制state */
    store.replaceState(targetState)
  })

  /* 訂閱store的變化 */
  store.subscribe((mutation, state) => {
    devtoolHook.emit("vuex:mutation", mutation, state)
  })
}

如果已經(jīng)安裝了該插件,則會(huì)在windows對(duì)象上暴露一個(gè)__VUE_DEVTOOLS_GLOBAL_HOOK__。devtoolHook用在初始化的時(shí)候會(huì)觸發(fā)“vuex:init”事件通知插件,然后通過(guò)on方法監(jiān)聽“vuex:travel-to-state”事件來(lái)重置state。最后通過(guò)Store的subscribe方法來(lái)添加一個(gè)訂閱者,在觸發(fā)commit方法修改mutation數(shù)據(jù)以后,該訂閱者會(huì)被通知,從而觸發(fā)“vuex:mutation”事件。

最后

Vuex是一個(gè)非常優(yōu)秀的庫(kù),代碼量不多且結(jié)構(gòu)清晰,非常適合研究學(xué)習(xí)其內(nèi)部實(shí)現(xiàn)。最近的一系列源碼閱讀也使我自己受益匪淺,寫這篇文章也希望可以幫助到更多想要學(xué)習(xí)探索Vuex內(nèi)部實(shí)現(xiàn)原理的同學(xué)。

關(guān)于

作者:染陌

Email:answershuto@gmail.com or answershuto@126.com

Github: https://github.com/answershuto

Blog:http://answershuto.github.io/

知乎主頁(yè):https://www.zhihu.com/people/cao-yang-49/activities

知乎專欄:https://zhuanlan.zhihu.com/ranmo

掘金: https://juejin.im/user/58f87ae844d9040069ca7507

osChina:https://my.oschina.net/u/3161824/blog

轉(zhuǎn)載請(qǐng)注明出處,謝謝。

歡迎關(guān)注我的公眾號(hào)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/89387.html

相關(guān)文章

  • Vue源碼解析

    摘要:系列文章鏈接主要記錄自己開始學(xué)習(xí)的一些源碼閱讀基于版本介紹了構(gòu)造函數(shù)如何來(lái)的,以及其上的屬性方法原型方法靜態(tài)方法的過(guò)程發(fā)生了什么對(duì)象如何生成對(duì)象如何掛載到真實(shí)的節(jié)點(diǎn)介紹了的插入,的使用,實(shí)例的生成實(shí)例對(duì)象上的和對(duì)象的創(chuàng)建中的組件和的源碼 系列文章鏈接主要記錄自己開始學(xué)習(xí)Vue的一些源碼閱讀:基于2.5.8版本 Vue-SourceCode 介紹了 Vue構(gòu)造函數(shù)如何來(lái)的,以及其上的屬...

    zgbgx 評(píng)論0 收藏0
  • Vue源碼解析(五)-vuex

    摘要:提供了函數(shù),它把直接映射到我們的組件中,先給出的使用值為值為讓我們看看的源碼實(shí)現(xiàn)規(guī)范當(dāng)前的命名空間。在中,都是同步事務(wù)。同步的意義在于這樣每一個(gè)執(zhí)行完成后都可以對(duì)應(yīng)到一個(gè)新的狀態(tài)和一樣,這樣就可以打個(gè)存下來(lái),然后就可以隨便了。 Vue 組件中獲得 Vuex 狀態(tài) 按官網(wǎng)說(shuō)法:由于 Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的,從 store 實(shí)例中讀取狀態(tài)最簡(jiǎn)單的方法就是在計(jì)算屬性中返回某個(gè)狀態(tài),本...

    calx 評(píng)論0 收藏0
  • 關(guān)于Vue2一些值得推薦的文章 -- 五、六月份

    摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...

    sutaking 評(píng)論0 收藏0
  • 關(guān)于Vue2一些值得推薦的文章 -- 五、六月份

    摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...

    khs1994 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<