store/index.js
import Vue from 'vue'
import Vuex from '@/vuex'
// import logger from 'vuex/dist/logger.js'
Vue.use(Vuex)
// 实现内置插件,修改 store 后的日志插件
function logger() {
return function (store) {
let prevState = JSON.stringify(store.state);
store.subscribe((mutation, state) => {
// 所有的更新操作都基于 mutation(状态变化都是通过 mutation的)
// 如果直接手动的更改状态,此 scbscribe 是不会执行 commit()
console.log('prevState:' + prevState);
console.log('mutation:' + JSON.stringify(mutation));
console.log('currentState:' + JSON.stringify(state));
prevState = JSON.stringify(state);
})
}
}
// 实现 vuex-persists 插件(持久化 store)
function persists() {
return function (store) {
let localState = JSON.parse(localStorage.getItem('VUEX:STATE'))
if (localState) {
store.replaceState(localState);
}
// 和 mutation挂钩的
store.subscribe((mutation, rootState) => { // 状态变化了,想做一些其他事
// 状态发生变化就存 localStorage中
// 防抖
localStorage.setItem('VUEX:STATE', JSON.stringify(rootState));
});
}
}
let store = new Vuex.Store({ // vuex持久化插件?
plugins: [
logger(),
persists() // 每次状态变化都可以存入到 localStorage中
],
state: { // state = > data
name: 'zhangsan',
age: 12
},
mutations: { // method commit 同步更改状态
changeAge(state, payload) {
state.age += payload
}
},
actions: { // 异步操作 调用api接口 dispatch, 多次commit mutation
changeAge({ commit }, payload) {
setTimeout(() => {
commit('changeAge', payload);
}, 1000);
}
},
getters: { // 计算属性
myAge(state) {
return state.age + 10
}
},
strict: true, // 如果不是在 mutation 中操作的状态会发生警告
modules: { // 进行模块分割
// namespaced 能解决子模块和父模块的命名冲突文件,相当于增加了一个命名空间
// 如果没有 namespaced 默认 getters 都会被定义到父模块上,mutations 会被合并在一起,最终一起调用,有了命名空间就没有这个问题了
// 子模块的名字不能和父模块中的状态重名
a: {
namespaced: true,
state: {
name: 't1',
age: 10
},
// 所有的getters 都会被合并到跟上
getters: { // 首页一个模块 home 订单页一个模块 order 用户一个模块 user
myAge(state) {
return state.age + 20;
}
},
mutations: {
changeAge(state, payload) {
state.age += payload
}
},
modules: {
c: {
namespaced: true,
state: {
age: 100
},
mutations: {
changeAge(state, payload) {
state.age += payload
}
},
modules: {
d: {
namespaced: true,
state: {
age: 100
},
}
}
}
}
},
b: {
namespaced: true,
state: {
name: 'b'
},
}
}
})
export default store;
store.js
function installModule(store, rootState, path, module) { // a/b/c/d
// 获取 moduleCollection 类的实例
let ns = store._modules.getNamespace(path);
// module.state => 放到 rootState 对应的儿子里(按层级放)
if (path.length > 0) { // 儿子模块
// 需要找到对应父模块,将状态声明上去
// {name: 'zhangsan', age: '12', a: aState}
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
// 对象新增属性不能导致重新更新视图
store._withCommittting(() => {
// 将新增属性变成响应式的
Vue.set(parent, path[path.length - 1], module.state);
})
}
// ......
// 遍历 mutation 放入 store.mutation
module.forEachMutation((fn, key) => {
// {myAge:[fn,fn]},没有命名空间的时候,要合并相同名字的 mutation
store.mutations[ns + key] = store.mutations[ns + key] || [];
store.mutations[ns + key].push((payload) => {
store._withCommittting(() => {
// 防止 this 丢失
fn.call(store, getNewState(store, path), payload); // 先调用 mutation 再执行 subscirbe
})
// 插件的更新依赖于 mutations 的执行
store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
})
});
// ......
// 递归安装
module.forEachChildren((child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
function resetVM(store, state) {
let oldVm = store._vm;
store.getters = {}; // 初始化 store.getters
const computed = {}; // 取 getters 属性时,把他代理到 vue 实例的计算属性 computed 上
forEach(store.wrapperGetters, (getter, key) => {
computed[key] = getter;
// 给 store.getters 赋值
Object.defineProperty(store.getters, key, {
// 在 vue 组件中使用 vuex 的 getters 时,代理到 vue 实例 store._vm 上
get: () => store._vm[key]
})
});
// 需要将 state 变成响应式的,直接将 state 放到 vue 实例的 data 里面就可以了,这样 state 就是响应式的,会自动更新视图
store._vm = new Vue({
data: {
// 以 $ 开头的数据不会被挂载到实例上,但是会挂载到当前的 _data 上,减少一次代理
// $$ 开头,表示是 vuex 自己自定义的变量
$$state: state
},
computed,
});
if (oldVm) { // 重新创建实例后,需要将老的实例卸载掉
Vue.nextTick(() => oldVm.$destroy())
}
}
class Store {
constructor(options) {
// 对用户的模块进行整合,整合成一棵树
// 当前格式化完毕的数据,放到了 this._modules 里
this._modules = new ModuleCollection(options); // 对用户的参数进行格式化操作
this.wrapperGetters = {};
// 需要将模块中的所有的 getters、mutations、actions 进行收集
this.mutations = {};
this.actions = {};
this._subscribes = []; // 插件集合(发布订阅模式)
this._committing = false; // 标记:是否通过 mutation 修改的属性,默认不是在 mutation 中更改的
this.strict = options.strict;
// 没有 namespace 的时候 getters 都放在根上,actions 和 mutations 会被合并数组
let state = options.state;
// 将包装后的模块进行安装
installModule(this, state, [], this._modules.root);
// getters 计算属性的实现
resetVM(this, state);
// 说明用户使用了插件
if (options.plugins) {
options.plugins.forEach(plugin => plugin(this))
}
}
subscribe(fn) {
this._subscribes.push(fn);
}
replaceState(newState) { // 需要替换的状态
this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
// 虽然替换了状态,但是 mutation getter 中的 state 在初始化的时候,已经被绑定死了老的状态
// 所以在获取 state 的地方要改成 => 获取最新的状态(resetVM 方法)
}
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
vuex 命名空间的实现
上一篇
评论