资讯专栏INFORMATION COLUMN

Vue源码解析(五)-vuex

calx / 1936人阅读

摘要:提供了函数,它把直接映射到我们的组件中,先给出的使用值为值为让我们看看的源码实现规范当前的命名空间。在中,都是同步事务。同步的意义在于这样每一个执行完成后都可以对应到一个新的状态和一样,这样就可以打个存下来,然后就可以随便了。

Vue 组件中获得 Vuex 状态

按官网说法:“由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态”,本文结合下面的demo进行分析:

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: "
{{count}}
", computed: { count(){ return this.$store.state.count } } })

下面主要分析为什么可以通过this.$store直接访问vueStore对象。先看看Vue.use方法

  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其实是下面这个对象

{
  Store: Store,
  install: install,
  mapState: mapState,
  mapMutations: mapMutations,
  mapGetters: mapGetters,
  mapActions: mapActions,
  createNamespacedHelpers: createNamespacedHelpers
}

因此Vue.use(Vuex)其实想到于Vuex.install()

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;
    }
  }
}
mapState

通过computed属性可以获取到状态值,但是每一个属性都要通过this.$store.state访问不是很方便。vue 提供了 mapState 函数,它把state直接映射到我们的组件中,先给出mapState的使用demo

let mapState = Vuex.mapState
let vm = new Vue({
  el: "#app",
  store: vueStore,
  template:
  `
`, components:{ "my-component":{ template: "
{{count}}-{{num}}
", computed: mapState({ // {{count}}值为this.$store.state.count count: state => state.count, // {{num}}值为this.$store.state.num, num: "num"          }) } } })

让我们看看mapState的源码实现

//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)
  }
}
store响应式原理

Vue源码解析(二)中介绍过data的响应式原理:
1、对data进行observe,针对data属性调用Object.defineProperty设置getter和setter,同时绑定一个dep对象
2、new Watcher(vm, updateComponent, noop)监听整个dom的变化
3、watcher初始化时调用updateComponent,updateComponent调用render函数更新dom(此时还会将该watcher对象赋值给全局对象Dep.target,进行依赖收集)
4、在watcher对象依赖收集期间,render函数访问data中的属性(如本例的data.message),触发data.message的getter方法,在getter方法中会将data.message绑定的dep对象和wathcer对象建立对应关系(互相加入到对方维护的队列属性上)
5、后续data属性的值变化时dep对象会通知所有依赖此data属性的watcher对象调用updateComponent方法更新视图
store响应式的原理也是类似的,new Vuex.Store的过程也会对state进行observe

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响应式相同

mutation

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上对应的方法

Store.prototype.commit = function commit (type, payload) {
    var entry = this._mutations[type];
    entry.forEach(function commitIterator (handler) {
        handler(payload);
    });
}
actions
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的触发时间线一目了然

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89885.html

相关文章

  • 关于Vue2一些值得推荐的文章 -- 、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    khs1994 评论0 收藏0
  • 前方来报,八月最新资讯--关于vue2&3的最佳文章推荐

    摘要:哪吒别人的看法都是狗屁,你是谁只有你自己说了才算,这是爹教我的道理。哪吒去他个鸟命我命由我,不由天是魔是仙,我自己决定哪吒白白搭上一条人命,你傻不傻敖丙不傻谁和你做朋友太乙真人人是否能够改变命运,我不晓得。我只晓得,不认命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出处 查看github最新的Vue...

    izhuhaodev 评论0 收藏0
  • Vue.js资源分享

    摘要:中文官网英文官网组织发出一个问题之后,不要暂时的离开电脑,如果没有把握先不要提问。珍惜每一次提问,感恩每一次反馈,每个人工作还是业余之外抽出的时间有限,充分准备好应有的资源之后再发问,有利于问题能够高效质量地得到解决。 Vue.js资源分享 更多资源请Star:https://github.com/maidishike... 文章转自:https://github.com/maid...

    vpants 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<