按官网说法:“因为 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态”,本文结合下面的demo进行分析:vue
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const vueStore = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } }) let vm = new Vue({ el: '#app', store: vueStore, template: '<div>{{count}}</div>', computed: { count(){ return this.$store.state.count } } })
下面主要分析为何能够经过this.$store直接访问vueStore对象。先看看Vue.use方法react
Vue.use = function (plugin) { //插件只能注册一次 var installedPlugins = (this._installedPlugins || (this._installedPlugins = [])); if (installedPlugins.indexOf(plugin) > -1) { return this } //拼接参数,将Vue做为第一个参数 // additional parameters var args = toArray(arguments, 1); args.unshift(this); //调用plugin.install或plugin方法 if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args); } else if (typeof plugin === 'function') { plugin.apply(null, args); } installedPlugins.push(plugin); return this };
再看Vuex源码,Vuex实际上是下面这个对象git
{ Store: Store, install: install, mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, mapActions: mapActions, createNamespacedHelpers: createNamespacedHelpers }
所以Vue.use(Vuex)其实想到于Vuex.install()github
let Vue; // bind on install function install (_Vue) { Vue = _Vue; applyMixin(Vue); } var applyMixin = function (Vue) { var version = Number(Vue.version.split('.')[0]); //Vue2.0处理方法 if (version >= 2) { //将vuexInit方法注册到beforeCreate钩子上,当Vue的生命周期走到callHook(vm, 'beforeCreate');时触发vuexInit方法 Vue.mixin({ beforeCreate: vuexInit }); } // Vuex init hook, injected into each instances init hooks list. function vuexInit () { //this就是当前正在被new的Vue对象 var options = this.$options; //将options.store(本例demo中的vueStore)赋值给this.$store,所以能够经过this.$store访问vueStore对象 // store injection if (options.store) { this.$store = typeof options.store === 'function' ? options.store() : options.store; } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store; } } }
经过computed属性能够获取到状态值,可是每个属性都要经过this.$store.state访问不是很方便。vue 提供了 mapState 函数,它把state直接映射到咱们的组件中,先给出mapState的使用demovuex
let mapState = Vuex.mapState let vm = new Vue({ el: '#app', store: vueStore, template: `<div> <my-component></my-component> </div>`, components:{ 'my-component':{ template: '<div>{{count}}-{{num}}</div>', computed: mapState({ // {{count}}值为this.$store.state.count count: state => state.count, // {{num}}值为this.$store.state.num, num: 'num' }) } } })
让咱们看看mapState的源码实现redux
//normalizeNamespace规范当前vuex的命名空间。默认状况下,vuex内部的 action、mutation 和 getter 是注册在全局命名空间的,本例也是,所以namespace=‘’ var mapState = normalizeNamespace(function (namespace, states) { var res = {}; //规范states参数,将states转换为map格式,所以mapState支持多种写法 normalizeMap(states).forEach(function (ref) { var key = ref.key; var val = ref.val; res[key] = function mappedState () { var state = this.$store.state; var getters = this.$store.getters; if (namespace) { var module = getModuleByNamespace(this.$store, 'mapState', namespace); if (!module) { return } state = module.context.state; getters = module.context.getters; } return typeof val === 'function' ? val.call(this, state, getters) : state[val] }; // mark vuex getter for devtools res[key].vuex = true; }); //mapState其实就是提供简洁的写法将this.$store.state[val]赋值给coputed属性 return res }); 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] }); }) } //规范当前vuex的命名空间 function normalizeNamespace (fn) { return function (namespace, map) { if (typeof namespace !== 'string') { map = namespace; namespace = ''; } else if (namespace.charAt(namespace.length - 1) !== '/') { namespace += '/'; } return fn(namespace, map) } }
Vue源码解析(二)中介绍过data的响应式原理:
一、对data进行observe,针对data属性调用Object.defineProperty设置getter和setter,同时绑定一个dep对象
二、new Watcher(vm, updateComponent, noop)监听整个dom的变化
三、watcher初始化时调用updateComponent,updateComponent调用render函数更新dom(此时还会将该watcher对象赋值给全局对象Dep.target,进行依赖收集)
四、在watcher对象依赖收集期间,render函数访问data中的属性(如本例的data.message),触发data.message的getter方法,在getter方法中会将data.message绑定的dep对象和wathcer对象创建对应关系(互相加入到对方维护的队列属性上)
五、后续data属性的值变化时dep对象会通知全部依赖此data属性的watcher对象调用updateComponent方法更新视图
store响应式的原理也是相似的,new Vuex.Store的过程也会对state进行observesegmentfault
var Store = function Store (options) { var state = options.state //为了实现state的响应性new一个vue对象 // initialize the store vm, which is responsible for the reactivity resetStoreVM(this, state); } function resetStoreVM (store, state, hot) { //new一个vue对象对data(值为store.state)进行监听 store._vm = new Vue({ data: { $$state: state }, computed: computed }); }
后续实现和上面的data响应式相同promise
new Vuex.Store的过程当中会将mutation注册到store._mutations上架构
function registerMutation (store, type, handler, local) { var entry = store._mutations[type] || (store._mutations[type] = []); //封装mutation方法并push到store._mutations[type]上 entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload); }); }
当执行commit方法时就会执行store._mutations上对应的方法app
Store.prototype.commit = function commit (type, payload) { var entry = this._mutations[type]; entry.forEach(function commitIterator (handler) { handler(payload); }); }
const vueStore = new Vuex.Store({ state: { count: 1, }, mutations: { increment (state,payload) { state.count+=payload } }, actions: { increment (context,payload) { setTimeout(function () { context.commit('increment',payload) },1000) } } }) vueStore.dispatch('increment',10)
和mutation同样,new Vuex.Store也会将action注册到store._actions上,而后经过dispatch调用
function registerAction (store, type, handler, local) { var entry = store._actions[type] || (store._actions[type] = []); //包装action方法,传入store对象的commit方法和state等等 entry.push(function wrappedActionHandler (payload, cb) { var res = handler.call(store, { dispatch: local.dispatch, commit: local.commit, getters: local.getters, state: local.state, rootGetters: store.getters, rootState: store.state }, payload, cb); //action的返回不是promise会返回Promise.resolve(res) if (!isPromise(res)) { res = Promise.resolve(res); } return res }); } Store.prototype.dispatch = function dispatch (_type, _payload) { var entry = this._actions[type]; return entry.length > 1 ? Promise.all(entry.map(function (handler) { return handler(payload); })) : entry[0](payload) };
看到action和mutation的源码实现,你不由要问了,这不是基本同样的吗,那干吗还要画蛇添足?
vuex官网的解释:在 mutation 中混合异步调用会致使你的程序很难调试。例如,当你能调用了两个包含异步回调的 mutation 来改变状态,你怎么知道何时回调和哪一个先回调呢?这就是为何咱们要区分这两个概念。在 Vuex 中,mutation 都是同步事务。
知乎上有个问题“vuex中为何把把异步操做封装在action,把同步操做放在mutations?“,vue的做者尤雨溪的解释:事实上在 vuex 里面 actions 只是一个架构性的概念,并非必须的,说到底只是一个函数,你在里面想干吗均可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户本身的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态同样)。
同步的意义在于这样每个 mutation 执行完成后均可以对应到一个新的状态(和 reducer 同样),这样 devtools 就能够打个 snapshot 存下来,而后就能够随便 time-travel 了。
我我的的理解这是vuex的使用规范问题,mutation中使用异步也不会有大问题,可是按规范开发能让项目结构更清晰,调试更方便,下图是用vue devtool调试的vuex官方例子(https://github.com/vuejs/vuex...),mutation的触发时间线一目了然