vuex

vuex和单纯全局对象的不同

  • Vuex 的状态存储是响应式的
  • 限制直接修改, 保留每次修改的记录. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。
1
2
3
4
5
6
7
8
9
10
11
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.");
}
};

plugin初始化

  • vuex用法就是install时注册$store, 然后再new Vuex.Store传给vue option, 子组件便可以获取到根组件的state
  • 从$options里获取store, 没有就从parent获取, 设置到$store
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function install(_Vue) {
    if (Vue && _Vue === Vue) return
    Vue = _Vue
    Vue.mixin({ beforeCreate: vuexInit })
    }
    function vuexInit () {
    const 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
    }
    }

Store 实例化

  • Store的实例化过程拆成 3 个部分,分别是初始化模块,安装模块和初始化 store._vm
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    new Vuex.Store({
    actions,
    getters,
    state,
    mutations,
    modules
    // ...
    })
    function Store (options = {}) {
    var this$1 = this;
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
    install(window.Vue);
    }
    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); // 初始化模块
    this._modulesNamespaceMap = Object.create(null);
    this._subscribers = [];
    this._watcherVM = new Vue();
    // bind commit and dispatch to self
    // strict mode
    this.strict = strict;
    var state = this._modules.root.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); // 安装模块
    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreVM(this, state); // 初始化 store._vm
    // apply plugins
    plugins.forEach(function (plugin) { return plugin(this$1); });
    if (useDevtools) {
    devtoolPlugin(this);
    }
    };

ModuleCollection 初始化模块 生成_modules的树形结构数据

  • 注册根模板this.register([]:, rawRootModule: 模块组成, false: 是否运行时创建);
    • 根module没有路径[], 路径格式[‘parent’,’child’]常用reduce去逐层深入获取module对象
  • 创建modulevar newModule = new Module(rawModule, runtime);
  • 设置父子关系
  • 包含子模块递归注册
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // ModuleCollection
    var ModuleCollection = function ModuleCollection (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false);
    };
    ModuleCollection.prototype.get = function get (path) {
    return path.reduce(function (module, key) {
    return module.getChild(key)
    }, this.root)
    };
    ModuleCollection.prototype.getNamespace = function getNamespace (path) {
    var module = this.root;
    return path.reduce(function (namespace, key) {
    module = module.getChild(key);
    return namespace + (module.namespaced ? key + '/' : '')
    }, '')
    };
    ModuleCollection.prototype.update = function update$1 (rawRootModule) {
    update([], this.root, rawRootModule);
    };
    ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
    var this$1 = this;
    if ( runtime === void 0 ) runtime = true;
    if (process.env.NODE_ENV !== 'production') {
    assertRawModule(path, rawModule);
    }
    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 nested modules
    if (rawModule.modules) {
    forEachValue(rawModule.modules, function (rawChildModule, key) {
    this$1.register(path.concat(key), rawChildModule, runtime);
    });
    }
    };
    ModuleCollection.prototype.unregister = function unregister (path) {
    var parent = this.get(path.slice(0, -1));
    var key = path[path.length - 1];
    if (!parent.getChild(key).runtime) { return }
    parent.removeChild(key);
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Module
var Module = function Module (rawModule, runtime) {
this.runtime = runtime; // 除了根节点都是true
// Store some children item
this._children = Object.create(null); // 放子module
// Store the origin module object which passed by programmer
this._rawModule = rawModule;
var rawState = rawModule.state;
// Store the origin module's state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};
var prototypeAccessors = { namespaced: { configurable: true } };
prototypeAccessors.namespaced.get = function () {
return !!this._rawModule.namespaced
};
Module.prototype.addChild = function addChild (key, module) {
this._children[key] = module;
};
Module.prototype.removeChild = function removeChild (key) {
delete this._children[key];
};
Module.prototype.getChild = function getChild (key) {
return this._children[key]
};
Module.prototype.update = function update (rawModule) {
this._rawModule.namespaced = rawModule.namespaced;
if (rawModule.actions) {
this._rawModule.actions = rawModule.actions;
}
if (rawModule.mutations) {
this._rawModule.mutations = rawModule.mutations;
}
if (rawModule.getters) {
this._rawModule.getters = rawModule.getters;
}
};
Module.prototype.forEachChild = function forEachChild (fn) {
forEachValue(this._children, fn);
};
Module.prototype.forEachGetter = function forEachGetter (fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn);
}
};
Module.prototype.forEachAction = function forEachAction (fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn);
}
};
Module.prototype.forEachMutation = function forEachMutation (fn) {
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn);
}
};
Object.defineProperties( Module.prototype, prototypeAccessors );
1
2
3
4
5
store._modules.root[module]: {
state,
_children: [子module],
_rawModule: 用户定义
}

安装模块

  • 注册名称空间到store _modulesNamespaceMap(变量名前加路径dispatch('account/login'))
  • 设置响应式
  • makeLocalContext创建上下文
    • 3 个参数相关,store 表示 root store;namespace 表示模块的命名空间,path 表示模块的 path。
    • 子模块的里的Dispatch,commit遇到名称空间会自动拼上
    • state获取对应名称空间的state, getter返回同名称空间下的getter
  • 注册action,mutation,getter,递归安装子模块(拼上名称空间放到store._mutations, store._actions,store._wrappedGetters)
    • 要看传参就去看对应的registerxxxx函数在call时传入什么
      1
      2
      3
      4
      5
      6
      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); //
      });
      }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// store 表示 root store;state 表示 root state;path 表示模块的访问路径;module 表示当前的模块,hot 表示是否是热更新。
//installModule(this, state, [], this._modules.root)
function installModule (store, rootState, path, module, hot) {
var isRoot = !path.length;
var namespace = store._modules.getNamespace(path); // 'child/grandchild/'
// register in namespace map
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module; // 注册名称空间到store
}
// 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); // 设置响应式, 形成state树形结构
});
}
var local = module.context = makeLocalContext(store, namespace, path);
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);
});
module.forEachChild(function (child, key) {
installModule(store, rootState, path.concat(key), child, hot);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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);
}
};
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? function () { return store.getters; }
: function () { return makeLocalGetters(store, namespace); } // 返回名称空间下的getter
},
state: {
get: function () { return getNestedState(store.state, path); } // 返回名称空间下的state
}
});
return local
}
function makeLocalGetters (store, namespace) {
var gettersProxy = {};
var splitPos = namespace.length;
Object.keys(store.getters).forEach(function (type) { // getter中遍历
// skip if the target getter is not match this namespace
if (type.slice(0, splitPos) !== namespace) { return } // 是不是一个名称空间
// extract local getter type
var localType = type.slice(splitPos);
// Add a port to the getters proxy.
// Define as getter property because
// we do not want to evaluate the getters in this time.
Object.defineProperty(gettersProxy, localType, {
get: function () { return store.getters[type]; },
enumerable: true
});
});
return gettersProxy
}

初始化 store._vm

  • resetStoreVM 的作用实际上是想建立 getters 和 state 的联系,因为从设计上 getters 的获取就依赖了 state ,并且希望它的依赖能被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用 computed 计算属性来实现。
  • store.getters从computed获取,computed执行rawGetter(local.state,...) 方法那么就会访问到 store.state,进而访问到 store._vm._data.$$state,这样就建立了一个依赖关系。当 store.state 发生变化的时候,下一次再访问 store.getters 的时候会重新计算。
  • Object.defineProperties( Store.prototype, prototypeAccessors$1 ); store.state从store._vm._data.$$state
  • enableStrictMode store._vm会deep watch this._data.$$state直接修改state会警告, store.state 被修改的时候, store._committing 必须为 true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => 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
const silent = Vue.config.silent
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = 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(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}

API和语法糖

  • state,getter map到computed里. mutation,action map到 methods里

    mapState

  • normalizeNamespace自动加末尾斜杠, normalizeMap转key-value数组格式
  • getModuleByNamespace按名称空间获取module
  • 最终收集了computed的watcher到state.dep里
    1
    2
    3
    4
    mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var mapState = normalizeNamespace(function (namespace, states) {
var res = {};
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;
});
return res
});

模块动态更新

  • 刷新vm
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]
    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
    }
    unregisterModule (path) {
    if (typeof path === 'string') path = [path]
    if (process.env.NODE_ENV !== 'production') {
    assert(Array.isArray(path), `module path must be a string or an Array.`)
    }
    this._modules.unregister(path)
    this._withCommit(() => {
    const parentState = getNestedState(this.state, path.slice(0, -1))
    Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unregister (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
if (!parent.getChild(key).runtime) return
parent.removeChild(key)
}
function resetStore (store, hot) {
store._actions = Object.create(null)
store._mutations = Object.create(null)
store._wrappedGetters = Object.create(null)
store._modulesNamespaceMap = Object.create(null)
const state = store.state
// init all modules
installModule(store, state, [], store._modules.root, true)
// reset vm
resetStoreVM(store, state, hot)
}

插件

subscribe 的逻辑很简单,就是往 this._subscribers 去添加一个函数,并返回一个 unsubscribe 的方法。

而我们在执行 store.commit 的方法的时候,会遍历 this._subscribers 执行它们对应的回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
subscribe (fn) {
return genericSubscribe(fn, this._subscribers)
}
function genericSubscribe (fn, subs) {
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
commit (_type, _payload, _options) {
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// ...
this._subscribers.forEach(sub => sub(mutation, this.state))
}