既然 VUEX 可以 use,内部必定是有一个 install 方法,所以我们先要实现一个install方法,当我们用的时候,每一个组件上面都有一个this.$store属性,里面包含了状态仓库里面的state,mutations, actions, getters,所以我们也需要在每个组件上都挂载一个$store属性,具体实现如下:
let Vue = null;
export function install(_Vue) {
// 为了防止重复注册
if (Vue !== _Vue) {
Vue = _Vue
}
Vue.mixin({
beforeCreate() {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
})
}
为了后面的缓缓,我们需要封装一个自己的循环方法,用来简化代码操作
const forEach = (obj, cb) => {
Object.keys(obj).forEach(key => {
cb(key, obj[key]);
})
}
到此我们就可以正常的引入并use(vuex)啦,但是此时我们并没有去初始化仓库,因为原声的vuex还需要去 new Vuex.Store(),可以传入一些初始化参数,比如state、mutations、actions、getters,既然能 new ,说明这是一个 类,所以我们现在去写 Store 这个类,具体实现如下:
export class Store {
constructor(options = {}) {
// TODO....
}
}
好了,这个时候页面就不会报错啦,而且也可以通过 this.$store.state.xxx 取到state中的值了,但是原生的state重新设置会引发视图更新,所以还需要把store中的state设置成响应式的,具体实现如下:
export class Store {
constructor(options = {}) {
this.vm = new Vue({
data(){
return {
state: options.state
}
}
})
},
// 类的属性访问器
get state() {
return this.vm.state
}
}
到此我们在页面中设置值就可以引发视图更新啦,因为我们已经把所有的数据变成了双向绑定,只要视图更改就会引发视图更新这个时候,我们在初始化store的时候还传入了mutations、actions、getters,这些数据都还没有处理,所以现在我们就可以优先处理这些数据,而且在$store属性上还有 commit,dispatch,各种属性,所以我们还需要把这些属性页全部写好,具体实现如下:
export class Store {
constructor(options = {}) {
this.vm = new Vue({
data(){
return {
state: options.state
}
}
})
this.getters = {};
this.mutations = {};
this.actions = {};
// 处理 getters 响应式数据
let getters = options.getters;
forEach(getters,(getterName, fn)=>{
Object.defineProperty(this.getters, getterName, {
get: () => {
return fn(this.state)
}
})
})
// 获取所有的同步的更新操作方法
let mutations = options.mutations;
forEach(mutations,(mutationName, fn) => {
this.mutations[mutationName] = (payload) => {
fn(this.state, payload)
}
});
// 获取所有的异步的更新操作方法
let actions = options.actions;
forEach(actions,(actionName, fn) => {
this.actions[actionName] = (payload) => {
fn(this, payload);
}
})
}
commit = (type, payload) => {
this.mutations[type](payload)
}
dispatch = (type, payload) => {
this.actions[type](payload)
}
get state() {
return this.vm.state
}
}
到此,基本的vuex 已经可以正常运行啦!是不是很简单!!!!!
但是,到此还远远不止!!!!!!
我们知道,原生的 vuex 中还有modules 属性,里面可以嵌套任意层state,mutations,actions,getters,所以我们还需要处理这个属性
看原生的vuex属性中有个_modules 属性,是一个树结构
所以我们也仿照原生,生成一个__modules树结构的对象
// 格式化 _modules
class ModuleCollection {
constructor(options) {
// 注册模块 将模块注册成树结构
this.register([], options);
}
register(path, rootModule) {
let module = { // 将模块格式化
_rawModule: rootModule,
_chidlren: {},
state: rootModule.state
}
if (path.length == 0) {
// 如果是根模块 将这个模块挂在到根实例上
this.root = module;
} else {
// 递归都用reduce方法
// 通过 _children 属性进行查找
let parent = path.slice(0, -1).reduce((root, current) => {
return root._chidlren[current]
}, this.root)
parent._chidlren[path[path.length - 1]] = module
}
// 看当前模块是否有modules , 如果有modules 开始重新再次注册
if (rootModule.modules) {
forEach(rootModule.modules, (moduleName, module) => {
this.register(path.concat(moduleName), module)
})
}
}
}
然后在 Store 类中把 _modules 属性挂载到实例上
Store类中:
// 把数据格式化成一个 想要的树结构
this._modules = new ModuleCollection(options);
到此,我们把modules都已经处理成了想要的结构,但是各个模块下的属性都没有挂载处理,所以我们还需要递归挂载各个模块的属性,所以上面写的处理mutations、atcions、getters属性的方法都需要重新去写,具体实现如下:
/**
* @explain { 安装模块 }
* @param { store } 整个store
* @param { rootState } 当前的根状态
* @param { path } 为了递归来创建的
* @param { rootModule } 从根模块开始安装
*/
const installModule = (store, rootState, path, rootModule) => {
if (path.length > 0) {
// [a]
// 是儿子,儿子要找到爸爸将自己的状态 放到上面去
let parent = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
// vue 不能在对象上增加不存在的属性 否则不会导致视图更新
Vue.set(parent, path[path.length - 1], rootModule.state);
// {age:1,a:{a:1}}
// 实现了 查找挂在数据格式
}
// 以下代码都是在处理 模块中 getters actions mutation
let getters = rootModule._rawModule.getters;
if (getters) {
forEach(getters, (getterName, fn) => {
Object.defineProperty(store.getters, getterName, {
get() {
return fn(rootModule.state); // 让对应的函数执行
}
});
})
}
let mutations = rootModule._rawModule.mutations;
if (mutations) {
forEach(mutations, (mutationName, fn) => {
let mutations = store.mutations[mutationName] || [];
mutations.push((payload) => {
fn(rootModule.state, payload);
// 发布 让所有的订阅依次执行
store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
})
store.mutations[mutationName] = mutations;
})
}
let actions = rootModule._rawModule.actions;
if (actions) {
forEach(actions, (actionName, fn) => {
let actions = store.actions[actionName] || [];
actions.push((payload) => {
fn(store, payload);
})
store.actions[actionName] = actions;
})
}
// 挂载儿子
forEach(rootModule._chidlren, (moduleName, module) => {
installModule(store, rootState, path.concat(moduleName), module)
})
}
然后在 Store 类中执行此处理函数,完胜挂载
/**
* @explain { 安装模块 }
* @param { this } 整个store
* @param { this.state } 当前的根状态
* @param { [] } 为了递归来创建的
* @param { this._modules.root } 从根模块开始安装
*/
installModule(this, this.state, [], this._modules.root);
此时,我们自己写的 vuex 就可以完全正常运行啦,可以随意书写modules,state,mutations,actions,getters,是不是很酷!!!!!!哈哈哈哈但是我们还有一个 plugins 属性还没实现,具体这块的实现非常简单,因为我们在使用 plugins 的时候,传递一个数组,数组里面是一个个的 中间件函数,每一个函数的第一个参数都是 仓库实例本身,所以,我们在代码里就可以这样写
// 处理 插件
(options.plugins || []).forEach(plugin => plugin(this));
到此,我们的 vuex 就实现啦,是不是很简单,哈哈哈哈哈哈哈
下面是所有的代码:
const forEach = (obj, cb) => {
Object.keys(obj).forEach(key => {
cb(key, obj[key]);
})
}
let Vue = null;
export function install(_Vue) {
if (Vue !== _Vue) {
Vue = _Vue
}
Vue.mixin({
beforeCreate() {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
})
}
/**
* @explain { 安装模块 }
* @param { store } 整个store
* @param { rootState } 当前的根状态
* @param { path } 为了递归来创建的
* @param { rootModule } 从根模块开始安装
*/
const installModule = (store, rootState, path, rootModule) => {
if (path.length > 0) {
// [a]
// 是儿子,儿子要找到爸爸将自己的状态 放到上面去
let parent = path.slice(0, -1).reduce((root, current) => {
return root[current]
}, rootState)
// vue 不能在对象上增加不存在的属性 否则不会导致视图更新
Vue.set(parent, path[path.length - 1], rootModule.state);
// {age:1,a:{a:1}}
// 实现了 查找挂在数据格式
}
// 以下代码都是在处理 模块中 getters actions mutation
let getters = rootModule._rawModule.getters;
if (getters) {
forEach(getters, (getterName, fn) => {
Object.defineProperty(store.getters, getterName, {
get() {
return fn(rootModule.state); // 让对应的函数执行
}
});
})
}
let mutations = rootModule._rawModule.mutations;
if (mutations) {
forEach(mutations, (mutationName, fn) => {
let mutations = store.mutations[mutationName] || [];
mutations.push((payload) => {
fn(rootModule.state, payload);
// 发布 让所有的订阅依次执行
store._subscribes.forEach(fn => fn({ type: mutationName, payload }, rootState));
})
store.mutations[mutationName] = mutations;
})
}
let actions = rootModule._rawModule.actions;
if (actions) {
forEach(actions, (actionName, fn) => {
let actions = store.actions[actionName] || [];
actions.push((payload) => {
fn(store, payload);
})
store.actions[actionName] = actions;
})
}
// 挂载儿子
forEach(rootModule._chidlren, (moduleName, module) => {
installModule(store, rootState, path.concat(moduleName), module)
})
}
class ModuleCollection { // 格式化
constructor(options) {
// 注册模块 将模块注册成树结构
this.register([], options);
}
register(path, rootModule) {
let module = { // 将模块格式化
_rawModule: rootModule,
_chidlren: {},
state: rootModule.state
}
if (path.length == 0) {
// 如果是根模块 将这个模块挂在到根实例上
this.root = module;
} else {
// 递归都用reduce方法
// 通过 _children 属性进行查找
let parent = path.slice(0, -1).reduce((root, current) => {
return root._chidlren[current]
}, this.root)
parent._chidlren[path[path.length - 1]] = module
}
// 看当前模块是否有modules , 如果有modules 开始重新再次注册
if (rootModule.modules) {
forEach(rootModule.modules, (moduleName, module) => {
this.register(path.concat(moduleName), module)
})
}
}
}
export class Store {
constructor(options = {}) {
this.vm = new Vue({
data(){
return {
state: options.state
}
}
})
this.getters = {};
this.mutations = {};
this.actions = {};
this._subscribes = [];
// 把数据格式化成一个 想要的树结构
this._modules = new ModuleCollection(options);
/**
* @explain { 安装模块 }
* @param { this } 整个store
* @param { this.state } 当前的根状态
* @param { [] } 为了递归来创建的
* @param { this._modules.root } 从根模块开始安装
*/
installModule(this, this.state, [], this._modules.root);
// 处理 插件
(options.plugins || []).forEach(plugin => plugin(this));
}
subscribe(fn){
this._subscribes.push(fn);
}
commit = (type, payload) => {
this.mutations[type].forEach(cb => cb(payload))
}
dispatch = (type, payload) => {
this.actions[type].forEach(cb => cb(payload))
}
get state() {
return this.vm.state
}
}
export default {
install,
Store,
}
完啦,谢谢大家观看学习!哈哈哈哈