vuex 2.0源码解读(一)

转载请申明出处 https://segmentfault.com/a/11…

vuex2.0 和 vuex1.x 比拟,API转变的照样许多的,但基本思想没什么转变。vuex2.0 的源码挺短,四五百行的模样,两三天就能够读完。我是国庆时期断断续续看完的,写一下本身的邃晓。这里运用的vuex版本是 2.0.0-rc6。在看这篇文章之前,发起先看一遍官方的vuex2.0 文档,相识基本观点,不然以后的内容邃晓起来会很费力。

引入 vuex 文件

要想运用 vuex 有几种体式格局, 这里不细讲。

  • CDN

<script src='path/vue.js'><script> <!-- 必需先引入 vue -->
<script src='path/vuex.js'></script> <!-- 日常平凡进修时发起运用完整版 -->
  • ES6语法 + webpack

import Vuex from 'vuex'
var store = new Vuex.Store({})
Vuex.mapState({})

或许

import { Store, mapState } from 'vuex'
var store = new Store({})
mapState({})

Store组织函数

vuex 只暴露出了6个要领,分别是

var index = {
Store: Store,
install: install,
mapState: mapState,
mapMutations: mapMutations,
mapGetters: mapGetters,
mapActions: mapActions
}

return index;

个中 install 要领是合营 Vue.use 要领运用的,用于在 Vue 中注册 Vuex ,和数据流关联不大。其他的几种要领就是我们经常使用的。

先看看 Store 要领,进修 vuex 最早接触到的就是 new Store({}) 了。那末就先看看这个 Store 组织函数。

var Store = function Store (options) {
  var this$1 = this; // 指向返回的store实例
  if ( options === void 0 ) options = {};

  // 运用组织函数之前,必需保证vuex已注册,运用Vue.use(Vuex)注册vuex
  assert(Vue, "must call Vue.use(Vuex) before creating a store instance.")
  // 须要运用的浏览器支撑Promise
  assert(typeof Promise !== 'undefined', "vuex requires a Promise polyfill in this browser.")

  var state = options.state; if ( state === void 0 ) state = {};
  var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
  var strict = options.strict; if ( strict === void 0 ) strict = false;

  // store internal state
  // store的内部状况(属性)
  this._options = options
  this._committing = false
  this._actions = Object.create(null)  // 保留actions
  this._mutations = Object.create(null) // 保留mutations
  this._wrappedGetters = Object.create(null) // 保留包装后的getters
  this._runtimeModules = Object.create(null) 
  this._subscribers = []
  this._watcherVM = new Vue()

  // bind commit and dispatch to self
  var store = this
  var ref = this;
  var dispatch = ref.dispatch; // 援用的是Store.prototype.dispatch
  var commit = ref.commit; // 援用的是Store.prototype.commit 
  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
  this.strict = strict // 是不是开启严厉情势

  // init root module.
  // this also recursively registers all sub-modules
  // and collects all module getters inside this._wrappedGetters
  // 初始化 root module
  // 同时也会递归初始化一切子module
  // 而且网络一切的getters至this._wrappedGetters
  installModule(this, state, [], options)

  // initialize the store vm, which is responsible for the reactivity
  // (also registers _wrappedGetters as computed properties)
  // 重置vm实例状况
  // 同时在这里把getters转化为computed(盘算属性)
  resetStoreVM(this, state)

  // apply plugins
  plugins.concat(devtoolPlugin).forEach(function (plugin) { return plugin(this$1); })
};

一开始会有两个推断前提,推断 vuex 是不是已注册,和当前浏览器是不是支撑 Promise, assert 要领也挺简朴,假如传入的第一个参数为假值,则抛出一个毛病。

function assert (condition, msg) {
  if (!condition) { throw new Error(("[vuex] " + msg)) }
}

接着往下看,接着会定义 state, plugins,strict三个变量,分别是你传入的 options 对应的选项。以后就是定义返回的 store 实例的一些内部状况。先不要管它们详细是什么,这个以后会逐步讲,这里先看看 Store 组织函数都做了些什么。再以后就是绑定 dispatchcommit 要领到 store 实例上。接下来就是全部 vuex 的中心要领 installModule 了,以后重置 vm 实例的状况。

简朴点说,当你运用 Store 组织函数,它实际上做了这么几件事,起首定义给 store 实例定义一些内部属性,以后就是绑定 dispatchcommit 的上下文对象永远是 store 实例上,以后 installModule 依据传入的 options ‘充分’ 内部状况等等。

installModule

很主要的一个要领。贴上代码

/*
 * store 就是 store 实例
 * rootState 是运用组织函数options中定义的 state 对象
 * path 途径
 * module 传入的options
 */
function installModule (store, rootState, path, module, hot) {
  var isRoot = !path.length  // 是不是是root
  var state = module.state;
  var actions = module.actions;
  var mutations = module.mutations;
  var getters = module.getters;
  var modules = module.modules;

  // set state
  if (!isRoot && !hot) { 
    // 找到要注册的 path 的上一级 state
    var parentState = getNestedState(rootState, path.slice(0, -1))
    // 定义 module 的 name
    var moduleName = path[path.length - 1]
    // store._withCommit要领以后会讲
    // 这里先邃晓为 实行传入的函数
    store._withCommit(function () {
      // 运用Vue.set要领
      // parentState[moduleName] = state
      // 而且state变成响应式的
      Vue.set(parentState, moduleName, state || {})
    })
  }
  // 以后设置 mutations, actions, getters, modules
  if (mutations) {
    Object.keys(mutations).forEach(function (key) {
      registerMutation(store, key, mutations[key], path)
    })
  }

  if (actions) {
    Object.keys(actions).forEach(function (key) {
      registerAction(store, key, actions[key], path)
    })
  }

  if (getters) {
    wrapGetters(store, getters, path)
  }

  if (modules) {
    Object.keys(modules).forEach(function (key) {
      installModule(store, rootState, path.concat(key), modules[key], hot)
    })
  }
}

这里有个很主要的观点要邃晓,什么是 path. vuex 的一个 store 实例能够拆分红许多个 module ,差别的 module 能够邃晓成一个子代的 store 实例(事实上,module 确切和 store 具有一样的构造),这是一种模块化的观点。因而这里的 path 能够邃晓成是示意一种层级关联,当你有了一个 root state 以后,依据这个 root state 和 path 能够找到 path 途径对应的一个 local state, 每个 module 下的 mutations 和 actions 转变的都是这个local state,而不是 root state.

这里在 Store 组织函数里传入的 path 途径为 [],申明注册的是一个root state. 再看看上一段代码的末了

if (modules) {
    Object.keys(modules).forEach(function (key) {
      installModule(store, rootState, path.concat(key), modules[key], hot)
   })
 }

假如传入的options 中有 modules 选项,反复挪用 installModule, 这里传入的函数的 path 参数是 path.concat(key), 所以应当很好邃晓了。

简朴看一下 getNestedState 要领。

/*
 * state: Object, path: Array
 * 假定path = ['a', 'b', 'c']
 * 函数返回效果是state[a][b][c]
 */
function getNestedState (state, path) {
  return path.length
    ? path.reduce(function (state, key) { return state[key]; }, state)
    : state
}

reduce 要领接收一个函数,函数的参数分别是上一次盘算后的值,和当前值,reduce 要领的第二个参数 state 是初始盘算值。

registerMutation

假如 mutations 选项存在,那末就注册这个 mutations ,看一下它的完成。

/*
 * 注册mutations,也就是给store._mutations增添属性
 * 这里说一下handler
 * handler 是 mutations[key]
 * 也就是传入 Store组织函数的 mutations 
 */
function registerMutation (store, type, handler, path) {
  if ( path === void 0 ) path = [];

  // 在_mutations中找到对应type的mutation数组
  // 假如是第一次建立,就初始化为一个空数组
  var entry = store._mutations[type] || (store._mutations[type] = [])
  // 推入一个对原始mutations[key]包装过的函数
  entry.push(function wrappedMutationHandler (payload) {
    // store.state示意root state, 先猎取path途径下的local state
    // mutation应当是对path途径下的state的修正
    // 函数接收一个payload参数
    // 初始的handler,接收一个state he payload 参数
    handler(getNestedState(store.state, path), payload)
  })
}

逻辑很简朴,一切的 mutations 都经由处置惩罚后,保留在了 store._mutations 对象里。 _mutations 的构造为

_mutations: {
    type1: [wrappedFunction1, wrappedFuction2, ...],
    type2: [wrappedFunction1, wrappedFuction2, ...],
    ...
}

registerAction

function registerAction (store, type, handler, path) {
  if ( path === void 0 ) path = [];

  var entry = store._actions[type] || (store._actions[type] = [])
  var dispatch = store.dispatch;
  var commit = store.commit;
  entry.push(function wrappedActionHandler (payload, cb) {
    var res = handler({
      dispatch: dispatch,
      commit: commit,
      getters: store.getters,
      state: getNestedState(store.state, path),
      rootState: store.state
    }, payload, cb)
    // 假如 res 不是 promise 对象 ,将其转化为promise对象
    // 这是因为store.dispatch 要领里的 Promise.all()要领。
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(function (err) {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

这里一样是’充分’ store._actions 对象,每一种 action type 都对应一个数组,数组里寄存的包装后的 handler 函数,因为涉及到 promise,这里我想在下一节连系 store 的 dispatch 实例要领一同讲。

wrapGetters

/*
 * 包装getters函数
 * store增添一个 _wrappedGetters 属性
 * moduleGetters: 传入的options.getters
 * modulePath: 传入 installModule 函数的 path 
 */
function wrapGetters (store, moduleGetters, modulePath) {
  Object.keys(moduleGetters).forEach(function (getterKey) {
    var rawGetter = moduleGetters[getterKey] // 原始的getter
    if (store._wrappedGetters[getterKey]) { // 假如已存在,正告
      console.error(("[vuex] duplicate getter key: " + getterKey))
      return
    }
    store._wrappedGetters[getterKey] = function wrappedGetter (store) {
        // 接收三个参数
        // local state
        //  全局的 getters
        // 全局的 state
      return rawGetter(
        getNestedState(store.state, modulePath), // local state
        store.getters, // getters
        store.state // root state
      )
    }
  })
}

注重 这里的一切 getters 都储存在了全局的一个 _wrappedGetters 对象中,一样属性名是各个 getterKey ,属性值一样是一个函数,实行这个函数,将会返回原始 getter 的实行效果。

install modules

if (modules) {
    Object.keys(modules).forEach(function (key) {
      installModule(store, rootState, path.concat(key), modules[key], hot)
   })
 }

假如 options 中有 modules 选项,那末就递归挪用 installModule 要领,注重这里的 path 转变。

resetStoreVM

function resetStoreVM (store, state) {
  var oldVm = store._vm // 本来的_vm

  // bind store public getters
  store.getters = {} // 初始化 store 的 getters 属性为一个空数组。
  var wrappedGetters = store._wrappedGetters
  var computed = {} 
  Object.keys(wrappedGetters).forEach(function (key) {
    var fn = wrappedGetters[key]
    // use computed to leverage its lazy-caching mechanism
    // 将wrappedGetter中的属性转移到 computed 中
    computed[key] = function () { return fn(store); }
    // store.getters[key] = store._vm[key]
    Object.defineProperty(store.getters, key, {
      get: function () { return store._vm[key]; }
    })
  })
  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  // 设为 silent 情势
  var silent = Vue.config.silent
  Vue.config.silent = true
  // 初始化一个 store._vm 实例
  store._vm = new Vue({
    data: { state: state },
    computed: computed
  })
  Vue.config.silent = silent

  // enable strict mode for new vm
  // 启用严厉情势
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldVm) {
    // dispatch changes in all subscribed watchers
    // to force getter re-evaluation.
    store._withCommit(function () {
      oldVm.state = null
    })
    // 实行destroy 要领,关照一切的watchers 转变,并从新盘算getters的值。
    Vue.nextTick(function () { return oldVm.$destroy(); })
  }
}

这个要领在 installModule 要领以后实行,来看看它都做了什么。简朴点说,就是给 store 增添了一个 _vm 属性,指向一个新的 vue 实例,传入的选项包括一个 state 和 computed, computed 来自store 的 getters 属性。同时给 store 增添了一个 getters 属性,且 store.getters[key] = store._vm[key]

mapState

在讲 mapState 之前,先说一下基本要领 normalizeMap

/*
 * 假如map是一个数组 ['type1', 'type2', ...]
 * 转化为[
 *   {
 *     key: type1,
 *     val: type1
 *   },
 *   {
 *     key: type2,
 *     val: type2
 *   },
 *   ...
 * ]
 * 假如map是一个对象 {type1: fn1, type2: fn2, ...}
 * 转化为 [
 *   {
 *     key: type1,
 *     val: fn1
 *   },
 *   {
 *     key: type2,
 *     val: fn2
 *   },
 *   ...
 * ]
 */
function normalizeMap (map) {
  return Array.isArray(map)
    ? map.map(function (key) { return ({ key: key, val: key }); })
    : Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); })
}

normalizeMap 函数接收一个对象或许数组,末了都转化成一个数组情势,数组元素是包括 key 和 value 两个属性的对象。

再来看看 mapState 要领。

/*
 * states: Object | Array
 * 返回一个对象
 * 对象的属性名对应于传入的 states 的属性名或许数组元素
 * 属性值都是一个函数
 * 实行这个函数的返回值依据 val 的差别而差别
 */
function mapState (states) {
  var res = {}
  normalizeMap(states).forEach(function (ref) {
    var key = ref.key; 
    var val = ref.val; 

    res[key] = function mappedState () {
      return typeof val === 'function' // 假如是函数,返回函数实行后的效果
        ? val.call(this, this.$store.state, this.$store.getters)
        : this.$store.state[val] // 假如不是函数,而是一个字符串,直接在state中读取。
    }
  })
  return res 
}

mapState 函数实行的效果是返回一个对象,属性名对应于传入的 states 对象或许数组元素。属性值是一个函数,实行这个函数将返回响应的 state .

mapMutations

/*
 * mutations: Array
 * 返回一个对象
 * 属性名为 mutation 范例
 * 属性值为一个函数
 * 实行这个函数后将触发指定的 mutation 
 */
function mapMutations (mutations) {
  var res = {}
  normalizeMap(mutations).forEach(function (ref) {
    var key = ref.key; // mutation type
    var val = ref.val; // mutation type

    res[key] = function mappedMutation () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ]; // 一个数组缓存传入的参数

      // val作为commit函数的第一个参数type, 剩下的参数依次是payload 和 options
      return this.$store.commit.apply(this.$store, [val].concat(args))
    }
  })
  return res
}

注重这里传入的 mutations 只能是一个数组,数组元素的 mutation type . 函数的作用的也很简朴,传入一个 mutations 数组,返回一个对象,属性名是 mutation 的范例,属性值是一个函数,实行这个函数,将挪用 commit 来触发对应的 mutation 从而转变state。别的注重这里的 this 指向的 store 的 _vmmapState 是在 Vue 实例中挪用的。

mapActions

function mapActions (actions) {
  var res = {}
  normalizeMap(actions).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val;

    res[key] = function mappedAction () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];

      return this.$store.dispatch.apply(this.$store, [val].concat(args))
    }
  })
  return res
}

mapActions 函数和 mapMutations 函数险些千篇一律。唯一的区分纵然这里应当运用 dispatch 要领来触发 action.

mapGetters

/*
 * getters: Array
 */
function mapGetters (getters) {
  var res = {}
  normalizeMap(getters).forEach(function (ref) {
    var key = ref.key;
    var val = ref.val; 

    res[key] = function mappedGetter () {
      // 假如在getters中不存在,报错
      if (!(val in this.$store.getters)) {
        console.error(("[vuex] unknown getter: " + val))
      }
      // 依据 val 在 getters 对象里找对应的属性值
      return this.$store.getters[val]
    }
  })
  return res
}

这里 getters 一样接收一个数组,一样返回一个对象。

以上讲了四种 map*** 要领,这四种要领能够都返回一个对象,因而能够 ES6 新特征 ... 解构符。如

{
    ...mapState(options)
}

关于 ... 解构标记, 举个小例子就邃晓了

var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var obj2 = {
    ...obj1,
    d: 4
}
// obj2 = { a: 1, b: 2, c: 3, d: 4 }
// 一样能够用于数组
var arr1 = ['a', 'b', 'c']
var arr2 = [...arr1, 'd']
// arr2 = ['a', 'b', 'c', 'd'] 

install

install 要领与 vuex 数据流关联不大,主如果用于在 Vue 中注册 Vuex,这里为了坚持篇幅的完整性,简朴引见一下。

function install (_Vue) {
  if (Vue) { 
  // 报错,已运用了 Vue.use(Vuex)要领注册了
    console.error(
      '[vuex] already installed. Vue.use(Vuex) should be called only once.'
    )
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

// auto install in dist mode
// 在浏览器环境写,会自动挪用 install 要领
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue)
}

没什么难度,那就再看一下 applyMixin 要领

function applyMixin (Vue) {
  var version = Number(Vue.version.split('.')[0])
  // 搜检运用的 Vue 版本,初始化时的生命周期钩子函数是 init 照样 beforeCreate
  if (version >= 2) {
    var usesInit = Vue.config._lifecycleHooks.indexOf('init') > -1
    Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // 保留之前的 Vue.prototype._init
    var _init = Vue.prototype._init

    // 从新设置Vue.prototype._init
    Vue.prototype._init = function (options) {
      if ( options === void 0 ) options = {};
      //  初始化时先初始化vuexInit
      // options.init: Array  示意一组要实行的钩子函数
      //  options.init钩子函数之前加上了 vueInit
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }
  
  /*
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    var options = this.$options
    // store injection
    // 假如本身有store选项,用本身的
    // 不然查找父组件的
    if (options.store) {
      this.$store = options.store
    } else if (options.parent && options.parent.$store) {
      this.$store = options.parent.$store
    }
  }
}

解释写的很清晰了,那末再看看什么有是 vuexInit 函数, vuexInit 函数是 vuex 的生命周期钩子函数。函数传递了两个信息,(1)子组件能够有本身零丁的store,然则平常不这么做 (2) 假如子组件没有本身的 store ,就会查找父组件的。这也印证了 根组件的 store 会注入到一切的子女组件。

小结

以上讲解了 Vuex 暴露出的 6 种要领,也是 Vuex 里的用的最多的几种要领,以后还会解读一下其他一些要领,比方 store 的一些实例要领。

别的本文的 github 的地点为: learnVuex2.0

转载请申明原链接

全文完

    原文作者:499311496
    原文地址: https://segmentfault.com/a/1190000007108052
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞