转载请注明出处 https://segmentfault.com/a/11...javascript
vuex2.0 和 vuex1.x 相比,API改变的仍是不少的,但基本思想没什么改变。vuex2.0 的源码挺短,四五百行的样子,两三天就能读完。我是国庆期间断断续续看完的,写一下本身的理解。这里使用的vuex版本是 2.0.0-rc6。在看这篇文章以前,建议先看一遍官方的vuex2.0 文档,了解基本概念,否则以后的内容理解起来会很费劲。html
要想使用 vuex 有几种方式, 这里不细讲。vue
CDNjava
<script src='path/vue.js'><script> <!-- 必须先引入 vue --> <script src='path/vuex.js'></script> <!-- 平时学习时建议使用完整版 -->
ES6语法 + webpackreact
import Vuex from 'vuex' var store = new Vuex.Store({}) Vuex.mapState({})
或者webpack
import { Store, mapState } from 'vuex' var store = new Store({}) mapState({})
vuex 只暴露出了6个方法,分别是git
var index = { Store: Store, install: install, mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, mapActions: mapActions } return index;
其中 install
方法是配合 Vue.use
方法使用的,用于在 Vue 中注册 Vuex ,和数据流关系不大。其余的几种方法就是咱们经常使用的。github
先看看 Store 方法,学习 vuex 最早接触到的就是 new Store({})
了。那么就先看看这个 Store 构造函数。web
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
方法也挺简单,若是传入的第一个参数为假值,则抛出一个错误。vuex
function assert (condition, msg) { if (!condition) { throw new Error(("[vuex] " + msg)) } }
接着往下看,接着会定义 state
, plugins
,strict
三个变量,分别是你传入的 options 对应的选项。以后就是定义返回的 store 实例的一些内部状态。先不要管它们具体是什么,这个以后会慢慢讲,这里先看看 Store 构造函数都作了些什么。再以后就是绑定 dispatch
和 commit
方法到 store
实例上。接下来就是整个 vuex 的核心方法 installModule
了,以后重置 vm
实例的状态。
简单点说,当你使用 Store 构造函数,它实际上作了这么几件事,首先定义给 store
实例定义一些内部属性,以后就是绑定 dispatch
和 commit
的上下文对象永远是 store
实例上,以后 installModule
根据传入的 options
‘充实’ 内部状态等等。
很重要的一个方法。贴上代码
/* * 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 是初始计算值。
若是 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, ...], ... }
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 实例方法一块儿讲。
/* * 包装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 的执行结果。
if (modules) { Object.keys(modules).forEach(function (key) { installModule(store, rootState, path.concat(key), modules[key], hot) }) }
若是 options 中有 modules 选项,那么就递归调用 installModule
方法,注意这里的 path 改变。
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
以前,先说一下基础方法 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 .
/* * 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 的 _vm
。mapState
是在 Vue 实例中调用的。
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
.
/* * 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
方法与 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
转载请注明原连接
全文完