vuex源碼分析3.0.1(原創)

前言

chapter1 store構造函數vue

1.constructornode

2.get state和set statevuex

3.commit數組

4.dispatchpromise

5.subscribe和subscribeAction微信

6.watch和replaceStateapp

7.registerModule和unregisterModule異步

8.hotUpdate和_withCommitide

chapter2 export install函數

Q:Vuex如何實現裝載的?

chapter3 輔助函數

1.registerMutation、registerAction、registerGetter

2.enableStrictMode、getNestedState

3.unifyObjectStyle(type, payload, options)

1.store構造函數 /part1

1.constructor

源碼分析

 constructor (options = {}) {
    //安裝Vue對象
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      console.log("window.vue");
      install(window.Vue)
    }
   //開發環境對Vue、Promise和Store的判斷
    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.`)
    }
    //options包括插件選項、嚴格模式選項
    const {
      plugins = [],
      strict = false
    } = options

    // 存儲內部的狀態
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()

    // 綁定commit和dispatch
    const store = this
    const { dispatch, commit } = this
    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)
    }

    // 嚴格模式
    this.strict = strict

    const state = this._modules.root.state

    // 初始化根模塊,或者安裝子模塊
    installModule(this, state, [], this._modules.root)

    //初始化vm
    resetStoreVM(this, state)

    // 應用插件
    plugins.forEach(plugin => plugin(this))
    
    if (Vue.config.devtools) {
      devtoolPlugin(this)
    }
  }

2.get state和set state

ES6的get和set是取值和存值的函數,這是是對屬性state攔截存取行爲。

示例1

E:\vuex>node
//類的聲明,屬性prop進行存取攔截
> class MyClass {
...     constructor() {
.....           // ...
.....   }
...     get prop() {
.....           return 'getter';
.....   }
...     set prop(value) {
.....           console.log('setter: ' + value);
.....   }
... }
undefined
> let inst = new MyClass();
undefined
//設置prop時,根據程序邏輯會console.log
> inst.prop = 123;
setter: 123
123
//獲取prop,根據return返回"getter"字符串
> inst.prop
'getter'

源碼1

//取值返回的是this屬性
get state () {
    return this._vm._data.$$state
  }
//若是在非生產環境,那麼修改state就會使用assert打印錯誤信息
  set state (v) {
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `use store.replaceState() to explicit replace store state.`)
    }
  }

3.commit

 commit (_type, _payload, _options) {
    // check object-style commit檢查對象風格提交
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)
  //mutation的type判斷,也就是entry,若是不存在,那麼打印錯誤信息「不存在的mutation type」
    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;
    }
  //處理entry並訂閱它
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })
    this._subscribers.forEach(sub => sub(mutation, this.state))
//開發模式下的silent判斷
    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'
      )
    }
  }

(1)const { type, payload,options}=unify..........這是ES6的解構賦值。(node環境執行的哦)

示例2

E:\vuex>node
> const person = {
...   name: 'little bear',
...   age: 18,
...   sex: ''
... }
undefined
> let { name,age,sex } = person
undefined
> name
'little bear'

(2)this._withCommit(...)小括號內的部分整體上說是_withCommit的fn參數。

this._withCommit()中有對this._committing進行設置,首先this._committing = false賦值給中間變量,接下來提交前設爲true,fn調用結束後再經過中間變量設爲初始值。

接下來講說entry。entry就是mutations的type也就是某個函數。但是明明forEach方法是數組啊。其實經過this._mutations[type]獲取到就是一個數組。那麼對數組的元素handler進行調用。entry

相似以下內容:

(3)this._subscribers.forEach(sub => sub(mutation, this.state))是_subscribers遍歷收集來的actions並執行。咱們要注意到actions的使用也有commit提交,不過是異步的。因此這裏的actions執行是爲了補充剛剛同步提交的方式。

圖示1

(4)process.env.NODE_ENV !== 'production' &&options && options.silent

檢查選項,silent是靜默選項,若是使用了silent,那麼告知"silent已經被移除,請在dev-tool中使用過濾器功能。

4,dispatch

dispatch (_type, _payload) {
    // 檢查數組風格的分發
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
   //從this._actions拿到type對應的事件類型
    const entry = this._actions[type]
  //若是entry也就是事件類型不存在,那麼打印信息"vuex不知道的action類型"
    if (!entry) {
      if (process.env.NODE_ENV !== 'production') {
        console.error(`[vuex] unknown action type: ${type}`)
      }
      return
    }

    //_actionSubscribers遍歷每一個訂閱
    this._actionSubscribers.forEach(sub => sub(action, this.state))
    //若是entry.length大於1,那麼返回promise
    return entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)
  }

5.subscribe和subscribeAction

subscribe訂閱store的mutation。回調函數會在每一個mutaion完成時觸發。

示例

const myPlugin = store => {
            // 當 store 初始化後訂閱
            store.subscribe((mutation, state) => {
           //回調函數在每次mutation完成以後調用
                state.count++;
            })
        }
const store = new Vuex.Store({
            state:{
                count:5
            },
            mutations:{
                increment(state,payload){
                    state.count=state.count*payload;
                }
            },
            plugins: [myPlugin]
})
 //提交"increment"事件
store.commit("increment",20)
//最終store.state.count等於5*20+1=101。

subscribeAction訂閱action。回調函數會在每一個action完成時觸發。

const myPlugin2 = store => {
            // 當 store 初始化後訂閱
            store.subscribeAction((action, state) => {
           //每次action完成後回調函數都會被觸發
                state.huge--;
            })
        }
 const store = new Vuex.Store({
            state:{
                huge:2000
            },
            mutations:{
                REDUCE(state,payload){
                    state.huge=state.huge-payload
                }
            },
            actions:{
                reduce({commit,state},payload){
                    commit("REDUCE",payload)
                }
            },
            plugins: [myPlugin2]
        })
store.dispatch("reduce",500)
//store.state.huge結果2000-500-1等於1499

源碼分析

subscribe (fn) {
//fn即剛纔說的每次mutation以後的回調函數
    return genericSubscribe(fn, this._subscribers)
  }

  subscribeAction (fn) {
    return genericSubscribe(fn, this._actionSubscribers)
  }
//subscribe和subscribeAction返回的是一個箭頭函數
function genericSubscribe (fn, subs) {
//訂閱fn,那麼會push到this._subscribers或者this._actionSubscribers數組
  if (subs.indexOf(fn) < 0) {
    subs.push(fn)
  }
  return () => {
//箭頭函數在須要回調的時候再從數組裏裁剪出fn元素
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

 能夠看出,genericSubscribe功能是對訂閱數組的處理,先存進數組,須要的時候再取出來。

6.watch和replaceState

源碼分析

watch (getter, cb, options) {
//若是傳入的getter不是function,那麼打印信息"store.watch只接受一個函數"
    if (process.env.NODE_ENV !== 'production') {
      assert(typeof getter === 'function', `store.watch only accepts a function.`)
    }
//返回Vue.$watch方法,響應式監聽() => getter(this.state, this.getters)返回的值
//若是發生變化,那麼cb回調函數觸發
//options包括選項:deep,選項:immediate
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

示例

<!--Vue API中$watch的用法-->
<div  id="app">
       <button @click="addOne">加一</button>
    </div>
    <script>
       let vm= new Vue({
            el:"#app",
            data:{
                a:0
            },
           created:function(){
              //$watch監聽第一個函數返回的只,一旦發生變化,那麼執行回調函數
               this.$watch(function(){
                   return this.a;
               },function(newValue,oldValue){
                   console.log(newValue)
               })
           },
            methods:{
                addOne(){
                   this.a=1;
                }
            }
        })

    </script>

示例

//replaceState總體替換state,變化引發回調發生
const store=new Vuex.Store({
           state:{
               count:0
           }
       })

       store.watch(function(){
           return store.state;
       },function(){
           console.log(store.state.count)//20
       })
store.replaceState({count:20})

示例

//經過mutation改變state,觸發watch回調
const store2=new Vuex.Store({
    state:{
        count:100
    },
    mutations:{
        ADDONE(state){
            state.count++;
        }
    }

})
store2.watch(function(){
    return store2.state.count
    },function(){
        console.log(store2.state.count) 
        //101
    }
)
store2.commit("ADDONE");

源碼分析

replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

經過傳入一個新state對象,替換舊state。

示例

const store=new Vuex.Store({
              state:{
                  count:1,
                  num:20
              }
 })
 store.replaceState({count:0});
//經過替換,舊的state不存在,只有更新後的state
store.state.count//等於0
store.state.num//undefined

7.registerModule和unregisterModule

示例

源碼分析

registerModule (path, rawModule, options = {}) {
   //傳入的第一個參數要麼是數組,要麼是字符串,字符串會轉化爲字符串爲元素的數組
    if (typeof path === 'string') path = [path]
   //開發環境下的調試信息
    if (process.env.NODE_ENV !== 'production') {
   //若是path不能轉爲數組或者不是數組,那麼打印"模塊path必須是字符串或者數組"
      assert(Array.isArray(path), `module path must be a string or an Array.`)
   //若是傳入的path爲[]空數組,那麼打印"不能使用registerModule來註冊根模塊"
      assert(path.length > 0, 'cannot register the root module by using registerModule.')
    }
    //在store._modules上註冊模塊
    this._modules.register(path, rawModule)
   //安裝模塊
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    //以當前state更新store.getters
    resetStoreVM(this, this.state)
  }

源碼分析

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // 註冊命名空間的映射數組_modulesNamespaceMap
  if (module.namespaced) {
    store._modulesNamespaceMap[namespace] = module
  }

  //hot,即options 能夠包含 preserveState: true 以容許保留以前的 state。用於服務端渲染。
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)
 //遍歷mutation並註冊mutation,會由於namespaced而不一樣
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })
//遍歷action並註冊action
  module.forEachAction((action, key) => {
    //若是action.root爲true,那麼type等於key索引值,
    //即全局action,不管是子模塊仍是子模塊的子模塊都如此
    
   //若是action.root爲false,那麼type直接取namespacType
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })
//遍歷getter並註冊getterts,會由於namespaced而不一樣
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
 //遍歷子模塊,並遞歸調用installModule
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

源碼分析

 unregisterModule (path) {
    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.`)
    }
   //取消註冊,那麼store._modules.root._children就不會定義myModule屬性了
    this._modules.unregister(path)
    this._withCommit(() => {
   //getNestedState獲取到父級state
      const parentState = getNestedState(this.state, path.slice(0, -1))
   //Vue刪除相應的module內容
      Vue.delete(parentState, path[path.length - 1])
    })
   //以當前的this重置store
    resetStore(this)
  }

8.hotUpdate和_withCommit

源碼分析

//熱重載
hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }

Vuex 支持在開發過程當中熱重載 mutation、module、action 和 getter。

 _withCommit (fn) {
   //每次提交的時候,內部代碼都會傳進來一個箭頭函數
    const committing = this._committing
    this._committing = true
    fn()
    this._committing = committing
  }

2.export install

示例

<script src="js/vue.js"></script>
    <!--這行語句安裝了window.Vue-->
    <script>
        let Vue;
        if (!Vue && typeof window !== 'undefined' && window.Vue) {
            console.log("window.vue");
            install(window.Vue)
        }
        function  install (_Vue) {
            Vue = _Vue
            console.log(Vue);
            //applyMixin(Vue)是爲了在Vue初始化以前(beforeCreate)來完成vuex的初始化
            //由於2版本才提供了beforeCreate這個鉤子函數
            //applyMixin主要邏輯:if (version >= 2) {Vue.mixin({ beforeCreate: vuexInit })} else {}
        }
    </script>

從中能夠看出vuex的初始化過程,以Vue2版本爲爲例:

源碼分析

export function install (_Vue) {
//那麼問題來了,爲何要使用let Vue這個文件一個全局變量呢?主要是爲了不重複安裝
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== 'production') {
      //若是已經安裝過,那麼Vue就等於window.Vue爲何呢?
      //Vue.use(plugin)方法會調用export的install方法,那麼調用中使用Vue=_Vue賦值語句
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

install調用邏輯分析:

3.輔助函數

1.registerMutation、registerAction、registerGetter

function registerMutation (store, type, handler, local) {
  //將type屬性添加到_mutations對象,其初始值爲空數組[]
  const entry = store._mutations[type] || (store._mutations[type] = [])
 //咱們應該記得mutation是一個函數,那麼function.call作一個繼承,local.state和payload都應用於store對象
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}
........
registerMutation(store, namespacedType, mutation, local)
function registerAction (stobre, type, handler, local) {
  //_actions具備type屬性,其初始值爲一個數組
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload, cb) {
  //繼承於store對象
    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,那麼至關於直接返回含有res內容的promise對象
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    //_devtoolHook判斷
    if (store._devtoolHook) {
      //攔截promise錯誤
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
     //返回res
      return res
    }
  })
}
.........
registerAction(store, type, handler, local)

咱們應該還記得action是能夠寫異步操做的。

function registerGetter (store, type, rawGetter, local) {
  //若是對應已getter存在,進入分支,打印說"vuex重複的getter鍵"
  if (store._wrappedGetters[type]) {
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
//經過當前local和store返回rawGetter對象
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

2.enableStrictMode、getNestedState

if (store.strict) {
    enableStrictMode(store)
  }
//enableStrictMode功能是容許new vm的嚴格模式
function enableStrictMode (store) {
 //偵聽this._data.$$state也就是state
  store._vm.$watch(function () { return this._data.$$state }, () => {
 //state變化,回調函數觸發   
 //store._committing爲False,那麼打印"不要在mutation處理器外部提交state
 if (process.env.NODE_ENV !== 'production') {
      assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    }
  //deep:true,跟蹤對象內部屬性的變化,sync:true,同步
  }, { deep: true, sync: true })
}

首先,getNestedState的功能是父級state對象。

function getNestedState (state, path) {
  return path.length
     //state爲初始值,接下來遍歷path數組,並以state[key]取得state對象
    ? path.reduce((state, key) => state[key], state)
    : state
}

那麼爲何這個key好比state["myModule"]的索引就能拿到對應的state呢?這是由於state對象長這個樣子。

示例

let vm= new Vue({
            el:"#app",
        })
       const store=new Vuex.Store({
           state:{
               count:0
           }
       })
       function getNestedState (state, path) {
           return path.length
               ? path.reduce((state, key) => state[key], state)
               : state
       }
       let myModule={
           state:{
               count:8
           }
       }
       store.registerModule("myModule",myModule)
       //找到父級state對象
      //["myModule"].slice(0,-1)等於[]
       let parentState=getNestedState(store.state,["myModule"].slice(0,-1))
        console.log(parentState)

結果以下:

3.unifyObjectStyle(type, payload, options)

首先運行一下這個函數,它能夠傳入3個參數(payload)。因爲process是nodejs環境的變量,那麼在nodejs環境中運行。

它的功能是把提交數據對象風格化

//nodejs環境輸入function代碼
E:\vuex>node
> function isObject (obj) {
...   return obj !== null && typeof obj === 'object'
... }function unifyObjectStyle (type, payload, options) {
...   if (isObject(type) && type.type) {
.....     options = payload
.....     payload = type
.....     type = type.type
.....   }
...
...  if (process.env.NODE_ENV !== 'production') {
...     assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
...
...   }
...
...   return { type, payload, options }
... }
undefined
//nodejs環境中調用剛剛定義的unifyObjectStyle。
> unifyObjectStyle("login",{name:"vicky",password:"123"})
{ type: 'login',
  payload: { name: 'vicky', password: '123' },
  options: undefined }
> unifyObjectStyle({type:"login",payload:{name:"vicky",password:"123"}})
{ type: 'login',
  payload: { type: 'login', payload: { name: 'vicky', password: '123' } },
  options: undefined }

它討論了兩種狀況。(1)若是type.type不存在,那麼就是以參數風格的提交,按照最終的對象格式return。(2)若是type.type存在,也就是對象風格的提交,那麼就讓對象的type和payload從新賦值。而後return。以最終實現對象風格的統一。

而process的部分是對type的值進行判斷,若是不是string,那麼assert一個報錯信息。

 

 

寫做不易,歡迎打賞!微信哦。

相關文章
相關標籤/搜索