只涉及最基础的挂载,具体api的使用,请自行学习。
// 下面代码是在store文件下的index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(vuex)
export default new Vuex.Store({}) // 通常我们把实例Vuex的代码,放到store文件下。
// 下面代码是在main.js
import store from "./store";
let v = new Vue({
router,
store, // 把store放到Vue的options中,这样vuex就能把store挂载到每一个vue实例下的$store属性
render: (h) => h(App),
}).$mount("#app");
Vue.use(vuex)挂载插件到Vue上。注意Vue.use()的使用一定 是在new Vue({})之前的Vue.use会查看插件有没有暴露install函数,有的话,会执行插件中的install函数。如果没有的话就执行该插件(这种情况下:插件默认暴露出来的要是一个方法)。Vue.use()具体实现代码如下function initUse(Vue) {
Vue.use = function (plugin) {
var installedPlugins =
// 这个this代表的是Vue实例
// 首先判断Vue构造函数上有没有_installedPlugins属性
// 这个属性代表Vue实例注册的插件
// 如果没有的_installedPlugins属性的话,就给它创建并赋值为[]
this._installedPlugins || (this._installedPlugins = []);
// 查看Vue.use(xx)传进来的插件有没有注册过
// 如果有注册过的话,返回Vue构造函数
if (installedPlugins.indexOf(plugin) > -1) {
return this;
}
// 如果没有注册过的话
// 获取Vue.use(xxx,xxx,xxx)中的除第一个参数以外的参数
var args = toArray(arguments, 1);
// 然后追加一个this到第一个参数
args.unshift(this);
// 如果插件中存在install属性,且是方法的话就执行这个方法
if (typeof plugin.install === "function") {
// 执行方法,并使install指向插件本身
plugin.install.apply(plugin, args);
} else if (typeof plugin === "function") {
// 直接执行
plugin.apply(null, args);
}
// 然后在installedPlugins注册组件
installedPlugins.push(plugin);
// 返回Vue构造函数
return this;
};
}
// 将list转换为数组,并删除list的前start个元素
function toArray(list, start) {
start = start || 0;
var i = list.length - start;
var ret = new Array(i);
while (i--) {
ret[i] = list[i + start];
}
return ret;
}
install执行的时候,第一个参数一定是Vue。这是在Vue.use()中处理的。而vuex中的install的功能就是通过Vue.mixin()混入一个beforeCreatd钩子函数,该钩子函数的作用就是判断main.js中有没有传入store对象。如果有的话就把store混入到所有vue实例的$store.这样我们就能在任何vue实例中使用$store了export const install = function (_Vue) {
Vue = _Vue;
// 使用vue提供的方法 Vue.mixin混入 方法和数据
Vue.mixin({
beforeCreate() {
let options = this.$options;
// console.log(this.$options.store);
if (options.store) {
this.$store = options.store;
} else {
this.$store = this.$parent && this.$parent.$store;
}
},
});
};
export default new Vuex.Store({}) import store from "./store";
let v = new Vue({
router,
store, // 把store放到Vue的options中,这样vuex就能把store挂载到每一个vue实例下的$store属性
render: (h) => h(App),
}).$mount("#app");
import { Store, install } from "./store";
export default {
Store,
install,
};
index.js可知store.js下默认暴露Store, installstore中state的处理是利用vue中data进行处理的。因为它是响应式的。我们通过this.$store.state读取数据的时候,实际走的时Store下的get state()方法得到vue实例下的datastore中getters的处理是利用vue的computed属性处理的。因为vuex希望使用getter的时候,如果getter中所依赖的state不变的话,不需要重复执行方法。所以就利用了computed缓存机制。通过遍历options.getters定义和getter同名的computed,然后在通过Object.defineProperty劫持store中的getters,使其读取getter的时候,实际读取的是vue实例下的同名的comuted属性mutations的调用,是通过commit函数去调用store中的mutations。vuex中通过遍历mutations,给store重新定义mutations。然后通过commit调用this._mutations[key] = (payload) => {
fn.call(this, this.state, payload);
};
commit = (type, payload) => {
this._mutations[type](payload);
};
actions的实现原理和mutations的实现原理一致。不过需要注意actions实际执行中的第一个参数应该是store实例,而不是state。这是和mutations是有区别的。this._actions = {};
foreach(options.actions, (fn, key) => {
this._actions[key] = (payload) => {
fn.call(this, this, payload);
};
});
dispatch = (type, payload) => {
this._actions[type](payload);
};
let Vue; // 这个Vue会在install赋值。这样我们就可以使用new Vue({})创建实例了
const foreach = (obj = {}, cb) => {
Object.keys(obj).forEach((key) => cb(obj[key], key));
};
class Store {
constructor(options) {
let state = options.state;
this.getters = {};
const computed = {};
foreach(options.getters, (fn, key) => {
computed[key] = () => {
return fn(this.state);
};
Object.defineProperty(this.getters, key, {
get: () => {
return this._vm[key];
},
});
});
this._mutations = {};
foreach(options.mutations, (fn, key) => {
this._mutations[key] = (payload) => {
fn.call(this, this.state, payload);
};
});
this._actions = {};
foreach(options.actions, (fn, key) => {
this._actions[key] = (payload) => {
fn.call(this, this, payload);
};
});
this._vm = new Vue({
data: {
$$state: state,
},
computed,
});
}
get state() {
return this._vm._data.$$state;
}
commit = (type, payload) => {
this._mutations[type](payload);
};
dispatch = (type, payload) => {
this._actions[type](payload);
};
}
const install = (_Vue) => {
Vue = _Vue;
// 使用vue提供的方法 Vue.mixin混入 方法和数据
Vue.mixin({
beforeCreate() {
let options = this.$options;
// console.log(this.$options.store);
if (options.store) {
this.$store = options.store;
} else {
this.$store = this.$parent && this.$parent.$store;
}
},
});
};
export { Store, install };
new Vuex.Store({{options}) 初始化store实例Store类中的constructor中通过调用ModuleCollection类进行模块收集,把用户传递的具有树形结构的options转换为具有树形结构的ModuleCollection实例,并且挂载到this.$store上去。我们可以通过this.$store._modules查看经过ModuleCollection处理过的数据类型。register(path,module)函数接收两个参数,表示为path下添加子模块,path是一个数组。如['a','c'],表示为a模块添加一个子模块c。第一次register时,path为[]表示为根模块添加。ModuleCollection类下有一个getNameSpace函数,获取当前path的命名空间前缀。比如传递了一个['a','b'],那么它会根据a模块和b模块是否存在namespaced属性,有的话 返回值就是a/b/import { foreach } from "../../MVuex/utils";
import Module from "./module";
export default class ModuleCollection {
constructor(options) {
this.register([], options);
}
register(path, rootModule) {
let newModule = new Module(rootModule);
// 把当前注册的模块上添加一个属性,这个属性指向自己
rootModule.rawModule = newModule;
if (path.length == 0) {
this.root = newModule;
} else {
// 下面就是通过reduce寻找父模块并把path参数的最后一项,添加到它的父级中去
// 找到父级
let parent = path.slice(0, -1).reduce((memo, current) => {
// return memo._children[current];
return memo.getChild(current);
}, this.root);
// 给父级添加子模块
// parent._children[path[path.length - 1]] = newModule;
parent.addChild(path[path.length - 1], newModule);
}
if (rootModule.modules) {
foreach(rootModule.modules, (module, moduleName) => {
// 这个时候register中的path就是 ['b'] ,['b','c']这种
// ['b']代表root下的b模块
// ['b','c']代表的就是b下的c模块
this.register([...path, moduleName], module);
});
}
}
// 获取命名空间
getNameSpace(path) {
let root = this.root;
return path.reduce((namespace, key) => {
root = root.getChild(key);
return namespace + (root.namespaced ? key + "/" : "");
}, "");
}
}
register函数中通过实例化Module类,创建一个一个子模块。Module类存在几个方法getChild:获取子元素就是获取module._chidlren addChild添加子模块,以上两个方法是在register调用的 。forEachMutation forEachAction forEachGetters forEachChild就是遍历Module的mutation actions getters children属性。在installModule中调用。// let newModule = {
// _raw: options,
// _children: {},
// state: options.state,
// };
import { foreach } from "../utils";
export default class Module {
constructor(rootModule) {
this._rawModule = rootModule;
this._children = {};
this.state = rootModule.state;
}
// 获取当前模块的namespaced属性
get namespaced() {
return this._rawModule.namespaced;
}
// 获取_children属性
getChild(key) {
return this._children[key];
}
// 添加_children属性
addChild(key, module) {
this._children[key] = module;
}
forEachMutation(fn) {
if (this._rawModule.mutations) {
foreach(this._rawModule.mutations, fn);
}
}
forEachAction(fn) {
if (this._rawModule.actions) {
foreach(this._rawModule.actions, fn);
}
}
forEachGetters(fn) {
if (this._rawModule.getters) {
foreach(this._rawModule.getters, fn);
}
}
forEachChild(fn) {
foreach(this._children, fn);
}
}
// 这是没有经过ModuleCollection类处理的结构
state: {
name: "home",
age: 18,
},
modules: {
a: {
state: {},
modules: {
c: {
state: {},
},
},
},
b: {
state: {},
},
},
// 下面是经过ModuleCollection处理过的数据类型
root = {
_raw: rootModule, // rootModule就是原始的modules对象
state: rootModule.state,
_children: {
a: {
_raw: rootModule,
_children: {
c: {
_raw: rootModule,
_children: {},
state: rootModule.state,
};
},
state: rootModule.state,
};
},
};
new Vuex.Store({options})中的options变成了具有树形结构的ModuleCollection对象。并且该对象在store实例上通过_module属性展示然后通过installModule()把module的属性定义到store上。与上面不同的是,上面只是把一个树形结构_module挂载到store上,而这个方法是把state,mutation,action这些属性挂载到store上。也是通过递归调用installModule实现.注意这个地方收集之后是没有模块结构的。相同名称的mutation,action放到了一个数组里面(前提是没有命名空间)
挂载之后我们调整commit.dispath函数。让其遍历store实例上的mutations以及actions.循环调用相同名称的mutation以及action
经过上面过程之后 mutation action getters都已经挂载到store实例上了。后面通过resetStoreVm创建vm实例让其挂载到store上。vm实例中处理state以及getters。当我们读取state的时候,其实读取的就是this.$store._vm._data.$$state.而我们读取getter的时候就是读取的vm实例上的computed属性
import applyMixin from "./mixin";
import ModuleCollection from "./module/module-collections";
import { foreach } from "../MVuex/utils";
let Vue;
function getState(store, path) {
return path.reduce((newState, current) => {
return newState[current];
}, store.state);
}
// 安装模块
function installModule(store, rootState, path, module) {
// module实例就是通过Module类创建的实例
let namespace = store._modules.getNameSpace(path);
// 先把各个子模块的state也以树形结构放到根模块的state上
if (path.length > 0) {
//如果是子模块就需要把子模块的状态定义到根模块上.也是具有树形结构的
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
Vue.set(parent, path[path.length - 1], module.state);
}
// 遍历当前模块的mutations,把mutation添加store实例上
module.forEachMutation((mutation, type) => {
// 判断store._mutations下有没有相同的mutation,有的话就push
store._mutations[namespace + type] =
store._mutations[namespace + type] || [];
store._mutations[namespace + type].push((payload) => {
store._withCommitting(() => {
mutation.call(store, getState(store, path), payload);
});
store._subscribers.forEach((sub) => sub({ mutation, type }, store.state));
});
});
// 遍历当前模块的actions,把mutation添加store实例上
module.forEachAction((action, type) => {
store._actions[namespace + type] = store._actions[namespace + type] || [];
store._actions[namespace + type].push((payload) => {
action.call(store, store, payload);
});
});
// 遍历当前模块的getters,把mutation添加store实例上
module.forEachGetters((getter, key) => {
// getter不存在模块化,所有模块的getter都是定义到根模块上
// 具有相同名字的getter会覆盖
store._wrappedGetters[namespace + key] = function () {
return getter(getState(store, path));
};
});
// 安装子模块
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
function resetStoreVm(store, state) {
let oldVm = store._vm;
const wrappedGetters = store._wrappedGetters;
let computed = {};
store.getters = {};
foreach(wrappedGetters, (fn, key) => {
computed[key] = function () {
return fn();
};
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
});
});
store._vm = new Vue({
data: {
$$state: state,
},
computed,
});
if (store.strict) {
store._vm.$watch(
() => store._vm._data.$$state,
() => {
console.log(store._committing, "数据改变了");
console.assert(store._committing, "在mutation之外更改了状态");
},
{ deep: true, sync: true }
);
}
if (oldVm) {
Vue.nextTick(() => {
oldVm.$destroy();
});
}
}
class Store {
constructor(options) {
// 格式化用户传入的参数,格式化成树形结构,更直观一些,后续模块画也更好操作
// 1. 将模块转化为一棵树
this._modules = new ModuleCollection(options);
// 2. 安装模块 将模块上的属性,定义到我们的store中
let state = this._modules.root.state;
this._mutations = {};
this._actions = {};
this._wrappedGetters = {};
this._subscribers = [];
this.strict = options.strict; //是否是严格模式
this._committing = false;
// this指当前store实例 state是指根state
installModule(this, state, [], this._modules.root);
// 将状态放到vue实例中
resetStoreVm(this, state);
// 如果存在插件的话就执行插件
if (options.plugins) {
options.plugins.forEach((fn) => {
fn(this);
});
}
}
get state() {
return this._vm._data.$$state;
}
commit = (type, payload) => {
this._mutations[type].forEach((fn) => {
fn(payload);
});
};
dispatch = (type, payload) => {
this._actions[type].forEach((fn) => {
fn(payload);
});
};
//动态注册模块的函数
registerModule(path, module) {
console.log("注册模块", path, module);
if (typeof path == "string") path = [path];
// 先注册模块,注册后store._module就有了该模块
this._modules.register(path, module);
// 安装模块
installModule(this, this.state, path, module.rawModule);
// 重新创建一个实例
// 原因是新加的模块中的state以及getters并不是响应式的
resetStoreVm(this, this.state);
}
//
subscribe(fn) {
this._subscribers.push(fn);
}
// 替换状态
replaceState(newState) {
this._withCommitting(() => {
this._vm._data.$$state = newState;
});
}
_withCommitting(fn) {
console.log("数据改变之前", this._committing);
let committing = this._committing;
this._committing = true; //在函数调用前标识_committing为true
fn();
this._committing = committing; //在函数调用后标识_committing为false
console.log("数据改变之后", this._committing);
}
}
const install = (_Vue) => {
Vue = _Vue;
applyMixin(Vue);
};
export { Store, install };
经过以上过程我们的vuex就基本实现了