preface

Vuex as a powerful state management tool is widely used in practical work, through learning vuex source code can help us solve the puzzle hidden in the heart for a long time.

For example, where is the global state of VUex stored? Why does the page update synchronously when you change the state in the store? How do action and mutation work together to modify the state? Why is it recommended to write asynchronous actions and mutation defined as synchronous?

Many students who have actually used Vuex believe they have these questions in mind. This article will abandon the way of straight source code from beginning to end, from the question and then combined with the scene to study the implementation process of source code, so as to answer the above questions one by one.

Core principles

To start with, let’s start with a simple version of Vuex and see how it responds to page changes.

Start by defining a Vue instance VM. Note that the VM only defines a data attribute. It does not bind to the DOM node on the page, and does not create any other attributes or methods.

Store is a constructor that creates the Store object, which assigns the VM to the Store object.

Object.defineproperties defines a state on the Store constructor prototype and sets the function that gets the state.

const vm = new Vue({ data:{ value:"hello world" } }) function Store(){ this._vm = vm; } Object.defineProperties(Store.prototype,{ state:{ configurable:true, get(){ return this._vm; } } }) const store = new Store(); // Create a store objectCopy the code

As you can see from the code above, a Store object created using the Store constructor uses a layer of proxy. For example, store.state.value gets the value directly from the GET function, which in turn gets the value from the VM. The value retrieved from store.state.value is actually retrieved from the data defined by the VM.

Vm is a Vue instance, and the state it defines is reactive, which indirectly makes Store. state reactive.

The Store object is ready to be injected into the page for use. Vue.mixin can do this easily (code below).

It adds a Created life cycle to each Vue instance. The function calls this.$options to see if the client passed the store property, and if it did, it assigns it to this.

This allows you to retrieve the Store object from within the Vue instance. Then we start developing the page and use new to build a Vue instance app.

App is mounted to the page node #app,store is assigned as a parameter, and a property value is defined in the calculated properties.

Vue.mixin({ created() { if(this.$options.store){ this.$store = this.$options.store; } } }) const app = new Vue({ el: '#app', store, computed:{ value(){ return this.$store.state.value; }}})Copy the code

Fill in {{value}} in the page template to see that the page renders hello World. It would be surprising to see the state defined by the VM eventually mapped to the app’s computed properties.

Data projection is only one thing. If you change vm.value = Hello at this point, the page will be rerendered and the content will become Hello.

At this point, the responsive principle of VUex has gradually become clear. Vm and APP are built Vue instances.

The VM’s data acts as a data warehouse, and the app uses the warehouse’s data. Since the VM’s data is reactive, changes to the VM also trigger a re-rendering of the APP template.

The above scenario is too simple; its data structure is just an object. In the actual development requirements, the page state is much more complex, we usually divide the store state into different modules to deal with.

Although it was simple to modify the vm state directly, direct modification was too violent and the errors were not easy to trace, so action and mutation were derived to modify the state.

Let’s take a closer look at how Vuex supports modular data and state modification.

Modular implementation

The vuex source code defines the Store constructor as follows. First, it defines a number of initialization attributes, including this._modules = new ModuleCollection(options).

Options is a developer-defined configuration item passed into the ModuleCollection to generate a root module _modules.

Then two very important methods are performed. One is installModule to install the module, and the other is resetStoreVM to handle state responsively.

var Store = function Store (options) { var this$1 = this; . 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); // Build module this._modulesNamespacemap = object.create (null); this._subscribers = []; this._watcherVM = new Vue(); // bind commit and dispatch to self var store = this; var state = this._modules.root.state; // Install installModule(this, state, [], this._modules.root); // State resetStoreVM(this, state); . };Copy the code

Building modular objects

New ModuleCollection(options) builds a module object and assigns it to this._modules(this refers to the Store instance object).

Let’s review the modular configuration of options defined by the developer.

Export default new vuex. store ({state, mutations, actions, modules: {home, // home module login // login module}})Copy the code

Mutations and Actions can be configured globally, and state,mutations and Actions can also be configured for each submodule.

The main reason to build the module object return here is because the configuration object defined by the developer is not easy to manipulate and calculate, so the original configuration object is converted into another data structure that is more convenient to use.

The ModuleCollection handles the following code for options, which eventually returns a module object. The first call is to register the root module, where PATH is an empty array.

The newModule directly initializes an instance object, newModule, which contains three properties :_children,_rawModule, and state.

  • _children: Submodule of the current module
  • _rawModule: configuration item of the current module (defined by the developeroption)
  • state: Indicates the data status of the current module

The register function is the core method. It constructs the module object newModule from the configuration item, and then determines whether the current module is the root module based on the length of the PATH array.

If it is the root module, assign newModule to the root attribute. If it is not the root module, it gets the parent module of the current module through path and assigns the current module to _children of the parent module.

Modules. If there is a submodule configuration, continue to recursively call register.

Var ModuleCollection = function ModuleCollection (rawRootModule) {// rawRootModule corresponds to options this.register([], rawRootModule, false); }; ModuleCollection.prototype.register = function register (path, rawModule, runtime) { var this$1 = this; var newModule = new Module(rawModule, runtime); if (path.length === 0) { this.root = newModule; } else { var parent = this.get(path.slice(0, -1)); Parent-addchild (path[path.length-1], newModule); } // register modules if (rawmodule.modules) {// Find that the current configuration exists in the submodule configuration forEachValue(rawModule.modules, function (rawChildModule, Register this$1. Register (path.concat(key), rawChildModule, Runtime); }); }}; Module.prototype.addChild = function addChild (key, module) { this._children[key] = module; }; Var Module = function Module (rawModule, rawModule) var Module = function Module (rawModule, rawModule) runtime) { this.runtime = runtime; this._children = Object.create(null); this._rawModule = rawModule; var rawState = rawModule.state; this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}; };Copy the code

The final goal of the previous round is to return a data object with the following data structure. This data contains a parent-child hierarchy, with each level having its own state.

{root:{{state: {userInfo: {... }}, _children:{home: Module {runtime: false, _children:{... }, _rawModule: {... }, state: {... }} login: Module {runtime: false, _children: {... }, _rawModule: {... }, state: {... }}}, _rawModule: {state: {... }, mutations: {... }, actions: {... }, modules: {... }}, namespaced: false } } }Copy the code

The returned data object is assigned to this._modules(this refers to the store instance).

Modular processing

When the data for this._modules is built, take the initial state from the root module and pass it in with other parameters to the installModule function to install the module (code below).

var Store = function Store (options) { ... this._modules = new ModuleCollection(options); . var state = this._modules.root.state; installModule(this, state, [], this._modules.root); . }Copy the code

The installModule source code is as follows. The module installation process can be divided into the following four steps.

  • Set the hierarchy of the state. To the root modulerootStateIs the initial value. Each submodule is named as a modulekey, where the data status of the submodule isvalueAssign to the state object of the parent module. For example, the generated data structure is as follows.
Store. _module = {root:{state:{user_info:null,//user_info is the status information of the root module home:{ //list is the state of the home module (sub-module), with the name of the sub-module ' 'key' 'and the state value assigned to the parent module on the state object list:[]}}}}Copy the code
  • The makeLocalContext function returns a local object containing dispatch, COMMIT,getters, and state. The returned local object gives the module object context.

  • Start registering actions, Mutations, and getters globally based on the local object above.

  • If the first three steps are completed, the current module is installed. If the current module is found to contain submodules, recursively call installModule to install each submodule.

function installModule (store, rootState, path, module, hot) { var isRoot = ! path.length; Var namespace = store._modules. GetNamespace (path); var namespace = store._modules. If (module.namespaced) {store._modulesNamespacemap [namespace] = module; } // Set the state hierarchy, the root module does not execute if (! isRoot && ! Var parentState = getNestedState(rootState, path.slice(0, -1)); Var moduleName = path[path.length-1]; Store._withCommit(function () {// Assign the current module state to vue. set(parentState, moduleName, module.state); }); } // dispatch,commit,getters, and state all operate on the contents of the current module. Local is given to module.context = makeLocalContext(store, namespace, path); Mutations and getters module. ForEachMutation (function (mutation, key) {var namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); }); module.forEachAction(function (action, key) { var type = action.root ? key : namespace + key; var handler = action.handler || action; registerAction(store, type, handler, local); }); module.forEachGetter(function (getter, key) { var namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); }); ForEachChild (function (child, key) {installModule(store, rootState, path.concat(key), child, hot); }); }Copy the code

Now look at the composition of the Local object and the registration process for Actions, Mutations, and getters.

The local object is the result of running makeLocalContext(Store, Namespace, Path), which will eventually be stored under module.context.

The makeLocalContext function defines a local variable named local, which contains the dispatch and commit functions, and the local getters and state are configured to return the local object.

As a whole, the local object exposes four properties: Dispatch, Commit,getters, and State. These four properties only serve the current module.

For example, if a developer configates a home module under the root module, the home module will have its own local object. The local object that executes dispatch or commit only executes the actions and mutations functions defined in the home module. In addition, getters and state also refer to the getters function and state in the home module.

The implementation of the four local properties will be covered later. The main process gets the Local object and starts registering actions, Mutations, and getters.

Module. forEachAction takes the actions defined by the developer in the module.

Store is the final instance object returned by Vuex, and type corresponds to the function name of the action. If it is in a submodule,type is the result of concatenating the module name with the function name. For example, actions under the home module define a getList(…). {… }, the corresponding type is home/getList.

The registerAction function officially starts registering actions. The Store constructor mentioned this._actions = above The purpose of the registerAction function is to insert a handler in the _actions section of the store Object. The key value of the Object is type.

module.forEachAction(function (action, key) {
    var type = namespace + key;
    var handler =  action;
    registerAction(store, type, handler, local);
});

function registerAction (store, type, handler, local) {
  var entry = store._actions[type] || (store._actions[type] = []);
  entry.push(function wrappedActionHandler (payload, cb) {
     ...
  });
}
Copy the code

The result of the final execution looks like this. Place all actions defined by each module in the _actions section of the store object.

store = { ... ƒ :{ƒ], the getList function under //home module [ƒ] // getUser function of the root module}... }Copy the code

Let’s look again at the concrete implementation inside the global actions function wrappedActionHandler. Its internal utility closure associates the global Store object with the local object of the current module and the actions function handler defined by the developer.

The wrappedActionHandler function executes the handler directly internally, but the dispatch,commit,getters, and state passed to the actions function are all fetched from local. This is why when a developer defines an actions function for a module, the parameters dispatch, COMMIT,getters, and state apply only to the current module.

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); if (! isPromise(res)) { res = Promise.resolve(res); } return res });Copy the code

For example, the home module defines a getList function, and its parameter state points to the state of the home module, while commit calls the setList function defined by mutations in the home module, rather than looking globally.

GetList ({state,commit}){ajax({id:state.id}). Then ((data)=>{ commit("setList",data); }}})Copy the code

The result returned by the handler is promised, which means that the wrappedActionHandler must return a Promise.

Action registration has been introduced, the bottom line is to extract the actions defined by each module to do a layer of encapsulation processing in the global store object under the _actions property.

Let’s look at the registration of Mutations and Getters. Mutations functions defined by each module are all extracted for a layer of encapsulation and plugged into _mutations under the global store object, while getters are plugged into _wrappedGetters under the store object.

module.forEachMutation(function (mutation, key) { var namespacedType = namespace + key; registerMutation(store, namespacedType, mutation, local); }); module.forEachGetter(function (getter, key) { var namespacedType = namespace + key; registerGetter(store, namespacedType, getter, local); }); // mutation function registerMutation (store, type, handler, local) { var entry = store._mutations[type] || (store._mutations[type] = []); entry.push(function wrappedMutationHandler (payload) { handler.call(store, local.state, payload); }); } function registerGetter (store, type, rawGetter, local) { if (store._wrappedGetters[type]) { return } store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // local state local.getters, // local getters store.state, // root state store.getters // root getters ) }; }Copy the code

Finally, after registering actions, Mutations, and getters, the data structure of the Store object becomes the following.

Store = {_actions:{home/getList: [ƒ], ƒ : [ƒ], getNav function getUser: ƒ, // Immediately the logout function home/setList, which is defined in // home, ƒ] // home module next mutations setNav function defined}, _wrappedGetters:{home/getFilterList: ƒ function defined in getters under ƒ wrappedGetter(store) // HomeCopy the code

So much effort has been put into transforming the Store’s data structure into this form. What does this form really do?

For those familiar with VUex, dispatch is used for calling actions and commit is used for calling mutations. Let’s now look at how dispatch and commit are defined under the Store object.

Within a page component, an action function can be triggered when we use this.$store.dispatch(type,payload). In terms of how it is called, dispatch usually passes two parameters, the first being the function name of the action and the second being the parameter, and commit is executed similarly.

See first dispatch of source code (below), call dispatch are usually introduced into the type and content. The Store. The prototype. The dispatch var entry = this. There are other key code _actions [type]. Type goes to the _actions of the store object and sends payload to execute the function.

This gives you an idea of why _actions,_mutations, and _wrappedGetters data structures are built under store.

That’s because dispatch and commit all they have to do is go to _actions,_mutations and find the handler and execute it. For example, dispatch(‘getUser’) executes getUser under the root module. If type is home/getList, then the dispatch execution is getList under the home module.

Dispatch has become a unified call entry, and dispatch can be called to any action under any module by changing the parameter form of type, thus realizing the modularization support of dispatch function. Commit has a similar implementation principle, but it obtains treatment from _mutations Function.

var Store = function Store (options) { ... 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) }; . } Store.prototype.dispatch = function dispatch (_type, _payload) { var this$1 = this; // check object-style dispatch var ref = unifyObjectStyle(_type, _payload); var type = ref.type; var payload = ref.payload; var action = { type: type, payload: payload }; var entry = this._actions[type]; var result = entry.length > 1 ? Promise.all(entry.map(function (handler) { return handler(payload); })) : entry[0](payload); return result.then(function (res) { return res }) }; Store.prototype.commit = function commit (_type, _payload, _options) { var this$1 = this; var ref = unifyObjectStyle(_type, _payload, _options); var type = ref.type; var payload = ref.payload; var options = ref.options; var mutation = { type: type, payload: payload }; var entry = this._mutations[type]; this._withCommit(function () { entry.forEach(function commitIterator (handler) { handler(payload); }); }); };Copy the code

The page component first calls this.$store.dispatch(“home/getList”), and the store.prototype. dispatch function responds by getting home/get from store._actions List corresponds to the handler.

This handler is generated in the registerAction function and corresponds to the wrappedActionHandler in the following code. The handler in this function is the function in the actions defined by the developer. When executing this handler, it changes its context and passes in the properties of the local object in this module as parameters.

Handler is a function defined by the developer in actions. After the business logic in the function is complete, it usually performs a commit operation to invoke mutation. The commit inside the function is the local.mit (which will be added later), and the underlying call to local.mit is the commit of the global store object, which goes to store._mutations for a treatment function based on type.

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); if (! isPromise(res)) { res = Promise.resolve(res); } return res });Copy the code

The corresponding treatment function for store._mutations, as explained above, is generated in the registerMutation function. Corresponds to the wrappedMutationHandler function in the code below. The handler in the function also corresponds to the function defined by the developer in mutations, which passes in local. State as a parameter for execution.

function registerMutation (store, type, handler, local) {
  var entry = store._mutations[type] || (store._mutations[type] = []);
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload);
  });
}
Copy the code

Generally, the mutations function manipulates state directly, and once the state data is changed, the page will be rendered again. As the process goes on, we understand that Actions and mutations are just a mechanism to change data.

Developers typically define asynchronous logic in actions to obtain data, and synchronous logic in mutations to manipulate data, which makes the entire process of modifying data easy to record and track, increasing the robustness of the application.

Action write asynchronous,mutation write synchronous

Mutation is defined as sync, but not async.

Let’s look again at the definitions of dispatch and commit. Inside the dispatch function, we get an array of handler entries via this._actions[type], and then we execute the handler through promise.all to return the value to result.

The wrappedActionHandler that generates the action function will return the Promise value. In other words, the return value of the entry handler must be a Promise. Therefore, the Promise T is still a Promise,result calls then and returns res.

Store.prototype.dispatch = function dispatch (_type, _payload) {
  var this$1 = this;
  var ref = unifyObjectStyle(_type, _payload);
    var type = ref.type;
    var payload = ref.payload;

  var action = { type: type, payload: payload };
  var entry = this._actions[type];

  var result = entry.length > 1
    ? Promise.all(entry.map(function (handler) { return handler(payload); }))
    : entry[0](payload);

  return result.then(function (res) {
    return res
  })
};

Store.prototype.commit = function commit (_type, _payload, _options) {
  var this$1 = this;
  var ref = unifyObjectStyle(_type, _payload, _options);
    var type = ref.type;
    var payload = ref.payload;
    var options = ref.options;

  var mutation = { type: type, payload: payload };
  var entry = this._mutations[type];

  this._withCommit(function () {
    entry.forEach(function commitIterator (handler) {
      handler(payload);
    });
  });
};
Copy the code

Call Dispatch on the page (code below) and use then to listen for the asynchronous action function to complete (code below). The addition of a layer of Promise processing to Dispatch makes asynchronous operations easy to listen to and track, which makes logging easier for other debugging tools.

If we look at the commit above, it gets the handler function internally and executes. What if we define an asynchronous operation in the mutations function to change state?

From a source point of view, there is no problem defining asynchronous operations, and eventually modifying the data in state will cause the page to be rendered again. However, because there is no Promise processing inside it like that in DISPATCH, the debugging tool cannot correctly record the state changes caused by the asynchronous operation, which is not conducive to the development process’s control of the entire operation process of the program and the tracking of errors. Therefore, it is not recommended to write asynchronous operation in the mutations function.

This.$store. Dispatch ("getList"). Then (()=>{// actions completed}) GetList ({state,commit}){return Promise(resolve)=>{ajax(...) .then((res)=>{ commit(res); resolve(res); })})}}}Copy the code

So far, have to dispatch, commit, the actions, mutations collaboration how to modify the data process of the introduction, but in this part, the local objects inside the realization of the four properties not speak, now take a look at it the principle.

Local implementation

The local object is the result of running makeLocalContext(Store, Namespace, path) and will eventually be stored in the Context property under the Module object.

The local object exposes four properties: Dispatch, Commit,getters, and state. These four properties only serve the current module (code below).

It first determines whether noNamespace exists. If noNamespace does not exist, it indicates that the current module is the root module and directly calls the dispatch method of the global Store object.

If the local dispatch exists, it is a submodule. In this case, the local dispatch corresponds to a function whose parameters also include type and payload.

However, we can find that there is no dispatch implementation details in it. Inside the function, it just concatenates type and namespace with strings, and finally calls the global store.dispatch method (commit processing is similar to dispatch, which will not be described again).

//store uses new vuex.store ({... Function makeLocalContext (store, namespace, path) {var noNamespace = namespace === "; var local = { dispatch: noNamespace ? store.dispatch : function (_type, _payload, _options) { var args = unifyObjectStyle(_type, _payload, _options); var payload = args.payload; var options = args.options; var type = args.type; if (! options || ! options.root) { type = namespace + type; } return store.dispatch(type, payload) }, commit: noNamespace ? store.commit : function (_type, _payload, _options) { var args = unifyObjectStyle(_type, _payload, _options); var payload = args.payload; var options = args.options; var type = args.type; if (! options || ! options.root) { type = namespace + type; } store.commit(type, payload, options); }}; Object.defineProperties(local, { getters: { get: noNamespace ? function () { return store.getters; } : function () { return makeLocalGetters(store, namespace); } }, state: { get: function () { return getNestedState(store.state, path); }}}); return local }Copy the code

Dispatch and commit in the local object are just a concatenation of type, and the dispatches and commit in the global store are still called.

State is returned by calling getNestedState(store.state, path) (code below).

Function getNestedState (state, path) {return path.length? path.reduce(function (state, key) { return state[key]; }, state) : state }Copy the code

Store. state is the state of the root module defined under global Store (described below). The path array is used to look down from the topmost root module until it returns a child module. If path is an empty array, return the status of the root module.

Getters under local will only trigger the module defined getters function (code below). It is implemented by directly iterating through all getters defined by getters under the global Store object (the implementation of Store. Getters is described below), then fetching only the function name belonging to the current module’s getters function, and setting up a layer of proxy to call Store Object’s function,store.getters, gets the handlers underneath from store._wrappedgetters.

function makeLocalContext(){ ... Object.defineProperties(local, { getters: { get: noNamespace ? function () { return store.getters; } : function () { return makeLocalGetters(store, namespace); }}}); return local; Function makeLocalGetters (store, getList) {// Getters (home, getList); namespace) { var gettersProxy = {}; var splitPos = namespace.length; Object.keys(store.getters).forEach(function (type) { if (type.slice(0, splitPos) ! == namespace) { return } var localType = type.slice(splitPos); Object.defineProperty(gettersProxy, localType, { get: function () { return store.getters[type]; }, enumerable: true }); }); return gettersProxy }Copy the code

After installModule is finished, the store object’s data structure looks like the following. Next, execute resetStoreVM(this, state) to complete the reactive processing of the data.

{... ƒ :{ƒ : home/getList: [ƒ], and getNav: ƒ // getUser function defined in actions, _mutations:{ƒ : [ƒ], // Open mutations: ƒ, setList function home/setNav defined in // home module ƒ] // home module next mutations setNav function defined}, _wrappedGetters:{home/getFilterList: ƒ wrappedGetter(store) function defined in getters under // home module}, _modules:{root:{context: {dispatch: ƒ, commit: ƒ, getter,state},// Local object of the root module state:{home: {list:[]} // List userInfo under the home module :{} // userInfo under the root module, _children: State: {list:[]}, _children: {}, _rawModule: {... } // Raw configuration}}, _rawModule: {... }, // Original configuration}}}Copy the code

State reactivity

var Store = function Store (options) { ... this._modules = new ModuleCollection(options); Var state = this._modules.root.state; // Install installModule(this, state, [], this._modules.root); // resetStoreVM(this, state); . }Copy the code

As you can see from installModule, the state of root module is a hierarchy consisting of parent and child levels, each of which has its own state (code below).

Store. _module = {root:{state:{user_info:null,//user_info is the status information of the root module home:{ //list is the state of the home module (sub-module), with the name of the sub-module ' 'key' 'and the state value assigned to the parent module on the state object list:[]}}}}Copy the code

Now pass the state of the root module into the resetStoreVM function for reactive processing. Getters (local) : store. Getters (local) : store.

Store._wrappedgetters stores getters handlers defined by all modules, which first run through wrappedGetters objects, getting the function name key and the handler fn. Then recombine the key and FN into functions and put them into a computed object.

The most critical code for resetStoreVM to perform responsiveness is that the Vue instance is rebuilt and assigned to store._VM, the root module state is assigned to $$state as the initial value, and computed is assigned to _VM as the computed attribute.

Store.getters resets the get method via Object.defineProperty, so getting getters from store.getters shifts to getting getters from computed, and then to getting handlers from store._wrappedgetters .

function resetStoreVM (store, state, hot) { ... store.getters = {}; var wrappedGetters = store._wrappedGetters; var computed = {}; forEachValue(wrappedGetters, function (fn, key) { computed[key] = function () { return fn(store); } Object.defineProperty(store.getters, key, { get: function () { return store._vm[key]; }, enumerable: true // for local getters }); }); store._vm = new Vue({ data: { $$state: state }, computed: computed }); . }Copy the code

So far, the implementation of getters, Dispatch, and Commit in the store object has been covered, but the most important one remains unmentioned: store.state.

The source code defines store.state in the following way, which directly blocks the way that set changes the value, so directly changing the store state will raise a warning.

The get function is referred to this._vm._data.$$state by the proxy. The data state of store is stored in another Vue instance’s $$state, so store.state is responsive.

var prototypeAccessors$1 = { state: { configurable: true } };

prototypeAccessors$1.state.get = function () {
  return this._vm._data.$$state
};

prototypeAccessors$1.state.set = function (v) {
  if (process.env.NODE_ENV !== 'production') {
    assert(false, "use store.replaceState() to explicit replace store state.");
  }
};

Object.defineProperties( Store.prototype, prototypeAccessors$1 );

Copy the code

Store object distribution

Vue.use(Vuex) triggers applyMixin to perform installation operations (the code below). ApplyMixin basically adds a mixin so that each newly created Vue instance will execute vuexInit.

After the vuexInit function completes, all Vue instances create a $store variable in their scope that points to the store object passed in when the root instance is initialized from the top level.

function applyMixin (Vue) { var version = Number(Vue.version.split('.')[0]); if (version >= 2) { Vue.mixin({ beforeCreate: vuexInit }); } function vuexInit () { var options = this.$options; // 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; }}}Copy the code

API implementation

mapState

Let’s start by looking at several common ways mapState is invoked on a page.

computed:{ ... MapState ("home",{// Get list list:"list"})}... computed:{ ... MapState ({list:state=>state.home.list // Get list of home modules})}... computed:{ ... MapState (["login_info"]) // Get root login_info}Copy the code

As can be inferred from the above invocation, the mapState execution eventually returns an object whose key is a string and whose value is a function. Deconstruct it in computed.

NormalizeNamespace handles the namespace. If you pass a namespace as home in the first call above, the namespace will be home/ in the function that normalizeNamespace handles.

In the following code res is the final object returned by mapState,key corresponds to the key used to calculate the property call, and value is another layer of encapsulation function mappedState.

The mappedState function first checks whether the module name namespace is sent. If namespace exists, the local object of the module is retrieved from the submodule object using the context property. Then the state and getters retrieved from the local object belong to the module only. The result returned is also the value fetched from the submodule. If the namespace does not exist, the value is directly derived from the state of the root module.

var mapState = normalizeNamespace(function (namespace, states) { var res = {}; normalizeMap(states).forEach(function (ref) { var key = ref.key; // In the above case,val may be a string of ' 'list' 'or... depending on how it is called. Function fn 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] }; }); return res });Copy the code

mapGetter

MapGetter calls are similar to mapState and are used for computed deconstruction in page components.

MapGetters returns an object res, whose key corresponds to the key used to calculate the property call, and whose value is another layer of encapsulation called mappedGetter.

The result of the mappedGetter function execution points to getters defined under the global Store object.

var mapGetters = normalizeNamespace(function (namespace, getters) { var res = {}; normalizeMap(getters).forEach(function (ref) { var key = ref.key; var val = ref.val; val = namespace + val; res[key] = function mappedGetter () { if (namespace && ! getModuleByNamespace(this.$store, 'mapGetters', namespace)) { return } return this.$store.getters[val] }; }); return res });Copy the code

MapAction and mapMutations

MapActions is usually called by extracting getUser from actions defined by the root module into the component Methods.

methods:{ ... mapActions(['getUser']) }Copy the code

The mapActions call is deconstructed in the Vue component’s methods, so it can be inferred that the mapActions function return value is also an object.

Getuser. res is the final object returned by mapActions, with ref.key as the key and mappedAction as the value.

The mappedAction function first checks whether the module name namespace is passed. If the namespace does not exist, it gets the Dispatch from the global Store object, otherwise it gets the Dispatch from the local submodule.

Val and args are passed in for dispatch execution, which is equivalent to simulating a call to Dispatch (type,payload) to trigger actions execution.

The code of mapMutation is similar to that of mapAction and can be analyzed in the same way.

var mapActions = normalizeNamespace(function (namespace, 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 ]; var dispatch = this.$store.dispatch; if (namespace) { var module = getModuleByNamespace(this.$store, 'mapActions', namespace); if (! module) { return } dispatch = module.context.dispatch; } return typeof val === 'function' ? val.apply(this, [dispatch].concat(args)) : dispatch.apply(this.$store, [val].concat(args)) }; }); return res });Copy the code

Stern said

This paper explores the overall context of VUEX implementation from the aspects of modular data processing, responsive transformation and common API implementation.

As you can see from the space above, much of the processing logic in VUEX is designed to support complex multi-module data structures. It is very easy to implement responsive design without the introduction of multiple modules.