vuex源码分析3.0.1(原创)

2018-08-02 06:21:50来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

前言

chapter1 store构造函数

1.constructor

2.get state和set state

3.commit

4.dispatch

5.subscribe和subscribeAction

6.watch和replaceState

7.registerModule和unregisterModule

8.hotUpdate和_withCommit

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一个报错信息。

 

 

写作不易,欢迎打赏!微信哦。

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:高德地图 JS API - 根据经纬度获取周边建筑地标

下一篇:使用jQuery快速高效制作网页交互特效——01 第一章 JavaScript基