一.简介
-
通过唯一的入口
mutation
更改 state -
Vuex
不能单独使用 依赖vue
下面让我们自己动手实现一个vuex吧。
二.实现 Vuex 模块
1.install 方法
当调用install
方法的时候,传入当前版本Vue
的构造函数,我把传入的\_Vue
保存在 Vue
变量,并且把它导出
这样的话,我当前用的 vue
和写项目的 vue
版本一致
let Vue;
export class Store {
constructor(options){}
}
export const install = (_Vue) =>{
Vue = _Vue;
//...
}
2.mixin 方法
qs
:怎么让所有组件都能访问到store
对象?
所有组件都能执行的方法,Vue.mixin({beforeCreate})
,拿到 store
挂载到自己的身上
拿到根组件的 store
,将它共享给每个组件
Vue.mixin({
beforeCreate() {
let options = this.$options;
if (options.store) {
this.$store = options.store;
} else {
if (this.$parent && this.$parent.$store) {
this.$store = this.$parent.$store;
}
}
},
});
3.实现 state
qs
:改变 store.state,怎么通知视图更新?
创建一个 vue 实例
把 store.state
放到 data
上
属性访问器 访问$store.state
代理到this._vm.$$state
import { Vue } from "./install";
class Store {
constructor(options) {
//用户组件中使用的$store 等价于这里的this
let { state, mutation, actions, moudle, strict, getters } = options;
this._vm = new Vue({
data: {
$$state: state,
},
});
}
get state(){
return this._vm._data.$$state
}
}
export default Store;
因为下面用多次遍历 所以我们在这边写一个遍历的方法,方便后续操作
export const _forEach = (obj, fn) => {
Object.keys(obj).forEach((key) => {
fn(obj[key], key);
});
};
4.实现 getters
vuex 里的 getters 就相当于 vue 的 computed
默认不执行,取值才执行,具有缓存。
把 getters
上的数据定义到 computed
上
同时对 把 getters 取值代理
到 computed 上
this.getters = {};
const computed = {}
_forEach(options.getters, (fn, key) => {
computed[key] = () => {
return fn(this.state);
}
Object.defineProperty(this.getters,key,{
get:()=> this._vm[key]
})
});
this._vm = new Vue({
data: {
$$state: state,
},
computed // 利用计算属性实现缓存
});
5.实现 mutations
把用户传入的 muations
遍历赋值到 Store
的 muations
上
commit
调用 muations
的方法
export class Store {
constructor(options) {
this.mutations = {};
_forEach(options.mutations, (fn, key) => {
this.mutations[key] = (payload) => fn.call(this, this.state, payload)
});
}
commit = (type, payload) => {
this.mutations[type](payload);
}
}
6.实现 actions
把用户传入的 actions
遍历赋值到Store
的 actions
上
dispatch
调用 actions
的方法
export class Store {
constructor(options) {
this.actions = {};
_forEach(options.actions, (fn, key) => {
this.actions[key] = (payload) => fn.call(this, this,payload);
});
}
dispatch = (type, payload) => {
this.actions[type](payload);
}
}
三.实现模块机制
nameSpaced
可以解决子模块和父模块的命名冲突文件,相当于增加了一个命名空间
1.格式化用户数据 options
对应的 moudle 放入对应的 children 下 格式化成如下格式 用树的结构来注册模块父子关系
this.root={
_raw:用户定义的模块,
state:当前模块自己的状态,
_children:{
a:{
_raw:用户定义的模块,
state:当前模块自己的状态,
_children:{
//子模块列表
}
}
}
}
path.length=0
,注册为根模块
再递归注册根模块的 modules
class ModuleCollection {
constructor(options) {
this.root = null;
this.register([], options);
}
register(path, rootModule) {
let newModule = {
_raw: rootModule,
_children: {},
state: rootModule.state,
};
if (path.length == 0) {
this.root = newModule;
} else {
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo._children[current];
}, this.root);
parent._children[path[path.length - 1]] = newModule;
}
if (rootModule.modules) {
forEach(rootModule.modules, (module, key) => {
this.register(path.concat(key), module);
});
}
}
}
2.抽离模块类
export default class Module {
constructor(rawModule) {
this._raw = rawModule;
this._children = {};
this.state = rawModule.state;
}
getChild(childName) {
return this._children[childName];
}
addChild(childName, module) {
this._children[childName] = module;
}
}
3.安装模块
没有 namespace 的时候,
遍历格式化后的数据 把 getters
都放在根上 mutations acrions
合并成数组
this._actions = {};
this._mutations = {};
this._wrappedGetters = {};
// 安装模块
installModule(this, state, [], this._modules.root);
addChild(childName, module) {
this._children[childName] = module;
}
forEachGetter(cb) {
this._raw.getters && _forEach(this._raw.getters, cb);
}
forEachMutation(cb) {
this._raw.mutations && _forEach(this._raw.mutations, cb);
}
forEachAction(cb) {
this._raw.actions && _forEach(this._raw.actions, cb);
}
forEachChildren(cb) {
this._children && _forEach(this._children, cb);
}
function installModule(store, rootState, path, root) {
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
// parent[path[path.length - 1]] = root.state;
//对象直接新增属性不能导致重新更新视图,要通过Vue.set
Vue.set(parent, path[path.length - 1], root.state);
console.log(rootState, "rootState");
}
root.forEachGetter((fn, key) => {
store.wrapperGetters[key] = function() {
return fn.call(store, root.state);
};
});
root.forEachMutation((fn, key) => {
store.mutations[key] = store.mutations[key] || [];
store.mutations[key].push((payload) => {
return fn.call(store, root.state, payload);
});
});
root.forEachAction((fn, key) => {
store.actions[key] = store.actions[key] || [];
store.actions[key].push((payload) => {
return fn.call(store, store, payload);
});
});
root.forEachChildren((child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
commit = (mutationName, payload) => {
this.mutations[mutationName] &&
this.mutations[mutationName].forEach((fn) => fn(payload));
};
dispatch = (actionName, payload) => {
this.actions[actionName] &&
this.actions[actionName].forEach((fn) => fn(payload));
};
4.定义 state 和 computed
function resetStoreVM(store, state) {
const computed = {};
store.getters = {};
const wrappedGetters = store._wrappedGetters
_forEach(wrappedGetters, (fn, key) => {
computed[key] = () => {
return fn(store.state);
}
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
});
store._vm = new Vue({
data: {
$$state: state,
},
computed
});
}
5.实现命名空间
nameSpaced
可以解决子模块和父模块的命名冲突文件,相当于增加了一个命名空间
如果没有 nameSpaced
默认 getters
会被定义到父模块上
import { forEachValue } from '../util';
import Module from './module';
export default class ModuleCollection {
getNamespace(path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key);
console.log(module)
return namespace + (module.namespaced ? key + '/' : '')
}, '');
}
}
export default class Module {
get namespaced(){
return !!this._rawModule.namespaced;
}
}
function installModule(store, rootState, path, root) {
+ let namespace = store._modules.getNamespace(path);
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current];
}, rootState);
Vue.set(parent, path[path.length - 1], root.state);
}
root.forEachGetter((fn, key) => {
+ store.wrapperGetters[namespace + key] = function() {
return fn.call(store, root.state);
};
});
root.forEachMutation((fn, key) => {
+ store.mutations[namespace + key] = store.mutations[namespace + key] || [];
+ store.mutations[namespace + key].push((payload) => {
return fn.call(store, root.state, payload);
});
});
root.forEachAction((fn, key) => {
+ store.actions[namespace + key] = store.actions[namespace + key] || [];
+ store.actions[namespace + key].push((payload) => {
return fn.call(store, store, payload);
});
});
root.forEachChildren((child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
6.注册模块
registerModule(path, module) {
if (typeof path == "string") path = [path];
this._modules.register(path, module);
installModule(this, this.state, path, module.newModule);
restVM(this, this.state);
}
function restVM(store, state) {
let oldVm = store._vm;
const computed = {};
store.getters = {};
forEach(store.wrapperGetters, (getter, key) => {
computed[key] = getter;
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
});
});
store._vm = new Vue({
data: {
$$state: state,
},
computed,
});
if (oldVm) {
//销毁上次创建的实例
Vue.nextTick(() => oldVm.$destroy());
}
}
四.实现插件功能
store = new Vuex.Store({
plugins: [logger(), persists("localStorage")],
)}
1.实现 logger
function logger() {
return function(store) {
let prevState = JSON.stringify(store.state);
store.subscribe((mutation, state) => {
console.log("prevState:", prevState);
console.log("mutation:", mutation);
console.log("currentState:", JSON.stringify(state));
prevState = JSON.stringify(state);
});
};
}
2.实现 persists 持久化插件
function persists() {
return function(store) {
let localState = JSON.parse(localStorage.getItem("VUEX:STATE"));
if (localState) {
store.replaceState(localState);
}
store.subscribe((mutation, rootState) => {
// 这里需要做一个节流 throttle lodash
localStorage.setItem("VUEX:STATE", JSON.stringify(rootState));
});
};
}
3.实现 plugins、subscribe、replaceState
class Store {
constructor(options) {
this._modules = new ModuleCollection(options);
installModule(this, state, [], this._modules.root);
//...
+ if (options.plugins) {
options.plugins.forEach((plugin) => plugin(this));
}
}
+ subscribe(fn) {
this._subscribes.push(fn);
}
+ replaceState(state){
this._vm._data.$$state = state;
}
//...
}
function installModule(store, rootState, path, root) {
let namespace = store._modules.getNamespace(path);
//...
root.forEachMutation((fn, key) => {
store.mutations[namespace + key] = store.mutations[namespace + key] || [];
store.mutations[namespace + key].push((payload) => {
fn.call(store, root.state, payload);
+ store._subscribes.forEach((fn) =>
fn({ type: namespace + key, payload }, rootState)
);
});
});
//...
}
4. 获取最新状态
function getNewState(store, path) {
return path.reduce((memo, current) => {
return memo[current];
}, store.state);
}
function installModule(store, rootState, path, root) {
let namespace = store._modules.getNamespace(path);
//...
root.forEachGetter((fn, key) => {
store.wrapperGetters[namespace + key] = function() {
+ return fn.call(store, getNewState(store, path));
};
});
root.forEachMutation((fn, key) => {
store.mutations[namespace + key] = store.mutations[namespace + key] || [];
store.mutations[namespace + key].push((payload) => {
+ fn.call(store, getNewState(store, path), payload);
store._subscribes.forEach((fn) =>
fn({ type: namespace + key, payload }, store.state)
);
});
});
}
五.辅助函数
1.mapState 实现
function mapState(stateList) {
let obj = {};
stateList.forEach((stateName) => {
obj[stateName] = function() {
return this.$store.state[stateName];
};
});
return obj;
}
2.mapGetters 实现
function mapGetters(gettersList) {
let obj = {};
gettersList.forEach((getterName) => {
obj[getterName] = function() {
return this.$store.getters[getterName];
};
});
return obj;
}
3.mapMutations 实现
function mapMutations(mutationList) {
let obj = {};
mutationList.forEach((mutationName) => {
obj[mutationName] = function(payload) {
this.$store.commit(mutationName, payload);
};
});
return obj;
}
4.mapActions 实现
function mapActions(actionList) {
let obj = {};
actionList.forEach((actionName) => {
obj[actionName] = function(payload) {
this.$store.dispatch(actionName, payload);
};
});
return obj;
}
六.区分 mutation 和 action
严格模式strict: true
,没有通过 mutation
修改 state
时,vuex
会提示如下错误
我们通过内部维护一个变量_committing
,来判断是不是在 mutaion
中更改的
_withCommitting(fn) {
this._committing = true;
fn(); //函数是同步的,获取_committing为true,如果fn是异步的,那么获取_committing为false
this._committing = false;
}
if (store.strict) {
store._vm.$watch(
() => store._vm._data.$$state,
() => {
console.assert(
store._committing,
"'在mutation之外更改了状态'"
);
},
{ deep: true, sync: true }
);
}
store._withCommitting(() => {
Vue.set(parent, path[path.length - 1], module.state);
})
replaceState(newState) { \
this._withCommitting(() => {
this._vm._data.$$state = newState;
})
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!