欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

vuex 源码分析(一) 使用方法和代码结构

程序员文章站 2022-10-09 23:10:55
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,注意:使用前需要先加载vue文件才可以使用(在node.js下需要使用Vue.use(Vuex)来安装vuex插件,在浏览器环境下直接加载即可,vuex会自行安装) vuex的使用方法很简单,首 ......

vuex 是一个专为 vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,注意:使用前需要先加载vue文件才可以使用(在node.js下需要使用vue.use(vuex)来安装vuex插件,在浏览器环境下直接加载即可,vuex会自行安装)

vuex的使用方法很简单,首先调用new vuex.store(options)创建一个store实例即可,然后在创建vue实例时把这个store实例作为store属性传入即可,调用new vuex.store(options)创建一个vuex实例时可以传入如下参数:

    state            存储的数据                
    getters           可以认为是store的计算属性
    mutations         这是更改vuex的store里的数据的唯一方法,只能是同步方法(官网这样写的,其实不赞同这个说法,具体请看下面)
    actions                  可以包含一些异步操作,它提交的是mutation,而不是直接变更状态。
    modules                为了更方便的管理仓库,我们把一个大的store拆成一些modules(子仓库),整个modules是一个树型结构
    strict                是否开启严格模式,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误,这能保证所有的状态变更都能被调试工具跟踪到。    ;默认为false
后面介绍每个api时单独介绍用法,举个栗子,如下:

 writer by:大沙漠 qq:22969969

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex@3.1.0/dist/vuex.js"></script>
</head>
<body>
    <div id="app">
        <p>{{message}}</p>
        <p>{{reversemessage}}</p>
        <p>{{no}}</p>
        <button @click="test1">mutation测试</button>
        <button @click="test2">action测试</button>
    </div>
    <script>
        const store = new vuex.store({
            state:{message:'hello world',no:123},                   
            getters:{                                   //getters类似于vue的计算属性
                reversemessage:state=>{return state.message.split('').reverse().join('')},
                increment:state=>{state.no++}
            },
            mutations:{                                 //mutation包含一些同步操作
                increment(state,payload){state.no+=payload.no}
            },
            actions:{                                   //actions包含一些异步操作
                increment({commit},info){
                    settimeout(function(){
                        commit('increment',info)
                    },500)
                }
            }
        })

        var app = new vue({
            el:"#app",
            store,
            computed:{
                no:function(){return this.$store.state.no},
                message:function(){return this.$store.state.message},
                reversemessage:function(){return this.$store.getters.reversemessage}
            },
            methods:{
                test1:function(){this.$store.commit('increment',{no:10})},
                test2:function(){this.$store.dispatch('increment',{no:10})}
            }
        })
    </script>
</body>
</html>

渲染如下:

vuex 源码分析(一)  使用方法和代码结构

我们点击mutation测试这个按钮123会这个数字会立马递增10,而点击action测试这个按钮,数字会延迟0.5秒,再递增10,前者是mutation对应的同步操作,而后者是action对应的异步操作

如果只是这样显式数据,感觉vuex没有什么用处,我们在浏览器里输入store.state.message="hello vue"来直接修改state里的数据看看怎么样,如下:

vuex 源码分析(一)  使用方法和代码结构

修改后页面里的内容立即就变化了,如下:

vuex 源码分析(一)  使用方法和代码结构

是不是很神奇,这里只是一个组件引用了vuex,如果很多的组件都引用了同一个vuex实例,那么只要状态发生变化,对应的组件都会自动更新,这就是vuex的作用。

vuex官网说mutations是更改store里数据的唯一方法,这在逻辑上不严谨的,只有设置了strict为true,那么说mutations是更改store里数据的唯一方法还可以接收,比如我们在控制台里直接修改store里的数据了,也没报错啥的。

vuex内部的实现原理很简单,就是定义一个vue实例,把vuex.store里的state作为data属性(不是根data,而是放到$$state这个属性里,不过由于值是个对象,因此也是响应式的),getters作为计算属性来实现的

 

源码分析


我们先看看vuex插件导出了哪些符号,打开vuex的源文件,拉到最底部,如下:

  var index = {
    store: store,                           //初始化
    install: install,                       //安装方法
    version: '3.1.0',                       //版本号
    mapstate: mapstate,                     //state辅助函数
    mapmutations: mapmutations,             //mutations辅助函数
    mapgetters: mapgetters,                 //getters辅助函数
    mapactions: mapactions,                 //actions辅助函数
    createnamespacedhelpers: createnamespacedhelpers
  };

可以看到store就是初始化函数,install是安装用的,version是版本号,其它几个都是辅助函数,最后一个是和辅助函数的上下文绑定(也就是命名空间)相关,一般用不到。

我们先看看安装流程,如下:

  function install (_vue) {         //安装vuex
    if (vue && _vue === vue) {        //如果veue存在且等于参数_vue,表示已经安装过了,则报错
      {
        console.error(
          '[vuex] already installed. vue.use(vuex) should be called only once.'
        );
      }
      return
    }
    vue = _vue;                       //将_vue保存到局部变量vue里   
    applymixin(vue);                  //调用applymixin()进行初始化
  }

安装时最后会执行applymixin函数,该函数如下:

  function applymixin (vue) {                         //将vuex混入到vue里面
    var version = number(vue.version.split('.')[0]);      //获取主版本号

    if (version >= 2) {                                   //如果是vue2.0及以上版
      vue.mixin({ beforecreate: vuexinit });                  //则执行vue.mixin()方法,植入一个beforecreate回调函数
    } else {
      // override init and inject vuex init procedure
      // for 1.x backwards compatibility.
      var _init = vue.prototype._init;
      vue.prototype._init = function (options) {
        if ( options === void 0 ) options = {};

        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 () {                                //vuex的安装方法  
      var options = this.$options;
      // store injection
      if (options.store) {                                    //如果options.store存在,即初始化vue实例时传入了store实例
        this.$store = typeof options.store === 'function'         //则将store保存到大vue的$store属性上,如果store是个函数,则执行该函数
          ? options.store()
          : options.store;
      } else if (options.parent && options.parent.$store) {   //如果options.store不存在,但是父实例存在$store(组件的情况下)
        this.$store = options.parent.$store;                      //则设置this.$store为父实例的$store
      }
    }
  }

这样不管是根vue实例,还是组件,都可以通过this.$store来获取到对应的$store实例了,安装就是这样子,下面说一下整体流程

以上面的例子为例,当我们执行new vuex.store()创建一个vuex.store的实例时会执行到导出符号的store函数,如下:

  var store = function store (options) {                  //构造函数
    var this$1 = this;
    if ( options === void 0 ) options = {};

    // auto install if it is not done yet and `window` has `vue`.
    // to allow users to avoid auto-installation in some cases,
    // this code should be placed here. see #731
    if (!vue && typeof window !== 'undefined' && window.vue) {      //如果局部变量vue不存在且window.vue存在,即已经引用了vue,而且window.vue不存在(还没安装)
      install(window.vue);                                              //执行install()方法进行安装     ;从这里看出在浏览器环境下不需要执行vue.use(vuex),在执行new vuex.store()会自己安装
    }

    {
      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.");
    }

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

    // store internal state
    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);                    //初始化modules,modulecollection对象是收集所有模块信息的
    this._modulesnamespacemap = 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;
    var commit = ref.commit;
    this.dispatch = function bounddispatch (type, payload) {          //重写dispatch方法,将上下文设置为当前的this实例
      return dispatch.call(store, type, payload)
    };
    this.commit = function boundcommit (type, payload, options) {     //重写commit方法,将上下文设置为当前的this实例
      return commit.call(store, type, payload, options)
    };

    // strict mode
    this.strict = strict;
  
    var state = this._modules.root.state;                             //获取根仓库的state信息

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedgetters
    installmodule(this, state, [], this._modules.root);               //安装根模块,该函数会递归调用的安装子模块,并收集它们的getters到this._wrappendgetters属性上

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedgetters as computed properties)
    resetstorevm(this, state);                                        //安装vm,也就是这里会创建一个vue实例,并把state、getter作为响应式对象

    // apply plugins
    plugins.foreach(function (plugin) { return plugin(this$1); });    //安装插件

    var usedevtools = options.devtools !== undefined ? options.devtools : vue.config.devtools;
    if (usedevtools) {
      devtoolplugin(this);
    }
  };

modulecollection模块会收集根模块和子模块的的所有信息,例子里执行到这里时对应的this._modules如下:

vuex 源码分析(一)  使用方法和代码结构

然后会调用执行到installmodule()会安装每个模块,也就是把每个模块的getters、mutations、actions进行一系列处理,如果还有子模块(module属性)则递归调用installmodule依次处理每个子模块,如下:

function installmodule (store, rootstate, path, module, hot) {      //安装模块 
    var isroot = !path.length;                                          //当前是否为根module
    var namespace = store._modules.getnamespace(path);                  //获取命名空间

    // register in namespace map
    if (module.namespaced) {
      store._modulesnamespacemap[namespace] = module;
    }

    // set state  
    if (!isroot && !hot) {                                             
      var parentstate = getnestedstate(rootstate, path.slice(0, -1)); 
      var modulename = path[path.length - 1];                          
      store._withcommit(function () { 
        vue.set(parentstate, modulename, module.state);
      });
    }

    var local = module.context = makelocalcontext(store, namespace, path);

    module.foreachmutation(function (mutation, key) {                     //遍历module模块的mutations对象
      var namespacedtype = namespace + key;
      registermutation(store, namespacedtype, mutation, local);             //调用registermutation注册mutation
    });

    module.foreachaction(function (action, key) {                         //遍历module模块的actions对象
      var type = action.root ? key : namespace + key; 
      var handler = action.handler || action;
      registeraction(store, type, handler, local);                          //调用registeraction注册action
    });

    module.foreachgetter(function (getter, key) {                         //遍历module模块的getter对象
      var namespacedtype = namespace + key;
      registergetter(store, namespacedtype, getter, local);                 //调用registergetter注册getter
    });

    module.foreachchild(function (child, key) {                           //如果有定义了module(存在子模块的情况)
      installmodule(store, rootstate, path.concat(key), child, hot);        //则递归调用installmodule
    });
  }

最后会执行resetstorevm()函数,该函数内部会创建一个vue实例,这样state和getters就是响应式数据了,如下:

  function resetstorevm (store, state, hot) {         //重新存储数据
    var oldvm = store._vm;

    // bind store public getters
    store.getters = {};
    var wrappedgetters = store._wrappedgetters;               //获取store的所有getter数组信息
    var computed = {};
    foreachvalue(wrappedgetters, function (fn, key) {         //遍历wrappedgetters
      // use computed to leverage its lazy-caching mechanism
      computed[key] = function () { return fn(store); };        //将getter保存到computed里面
      object.defineproperty(store.getters, key, {
        get: function () { return store._vm[key]; },
        enumerable: true // for local getters
      });
    });

    // use a vue instance to store the state tree
    // suppress warnings just in case the user has added
    // some funky global mixins
    var silent = vue.config.silent;                           //保存vue.config.silent的配置
    vue.config.silent = true;                                 //设置vue.config.silent配置属性为true(先关闭警告)
    store._vm = new vue({                                     //创建new vue()实例把$$state和computed变成响应式的
      data: {
        $$state: state
      },
      computed: computed
    });
    vue.config.silent = silent;                               //将vue.config.silent复原回去

    // enable strict mode for new vm
    if (store.strict) {
      enablestrictmode(store);
    }

    if (oldvm) {
      if (hot) {
        // dispatch changes in all subscribed watchers
        // to force getter re-evaluation for hot reloading.
        store._withcommit(function () {
          oldvm._data.$$state = null;
        });
      }
      vue.nexttick(function () { return oldvm.$destroy(); });
    }
  }

这样整个流程就跑完了,就是内部创建一个vue实例,利用vue的响应式做数据动态响应。