vuex 借助了vue的响应式,因此只能在vue中使用
$store属性挂载
Vue.use(vuex), 会给每个组件挂载上$store属性,便于store取值和方法的调用
- 利用全局的mixin中的beforeCreate声明周期,在每个组件挂载实例前,先挂载$store属性
- Vue渲染页面时,先渲染父组件,遇到子组件才会渲染子组件
- 如果new Vue 传入了store属性,说明是需要给子组件挂载$store属性,如果没有,则不会挂载
- 判断是否有options.store , 如果有,先给根实例挂载$store, 然后渲染子组件时,在将父组件的store挂载到当前组件的实例上
export let Vue;
function install(_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate(){ //this代表的是每个组件实例
// 获取根组件上的store 将他共享给每个组件
// 每个组件中都应该有$store
let options= this.$options;
if(options.store){
// 根
// console.log('根',options.name)
this.$store = options.store
}else{
// 先保证他是一个子组件,并且父亲上有$store
if(this.$parent && this.$parent.$store){
this.$store = this.$parent.$store
}
}
}
})
}
// 父 this.$store -》 子 this.$store -》孙子 this.$store
export default install
简单实现
借助Vue响应式和依赖收集的特性,简单实现
- state: 将state挂载到data中,当访问state属性时,代理到data中的state,进行依赖收集,修改state,通知更新(如果data中的属性以$开头,将不会把此属性代理到当前vue实例上,只能通过vm._data访问,因为我们的state建议只通过mutation更改,所以尽量少一层代理,减少用户直接更改的可能性)
- getters: 借助了Vue组件中的computed选项,遍历所有的getters,重写方法,将state传入,然后挂载Vue options中的computed属性上,每次访问getters的属性,代理到computed中,实现计算属性的作用(缓存,根据state计算)
- mutations: 遍历对象中的方法,将方法重写,改变this,传入state和payload
- actions: 遍历对象中的方法,将方法重写,this改成当前store,传入store和payload
class Store { // new Vue.Store 缠身一个实例
constructor(options) {
// 以下这些变量都是用户传递的
let { state, getters, mutations, actions, module, strict } = options;
this.getters = {}; // 我再取getters属性的时候 把他代理到计算属性上
const computed = {};
forEach(getters, (fn, key) => {
computed[key] = () => {
return fn(this.state); // 为了保证参数是state
}
// 当我们去getters上取值 需要对computed取值
Object.defineProperty(this.getters, key, {
get: () => this._vm[key] // 具备了缓存的功能
})
});
// ----------
this.mutations = {};
forEach(mutations, (fn, key) => {
this.mutations[key] = (payload) => fn.call(this, this.state, payload);
});
// ------dispatch中派发的是动作,里面可以有异步逻辑,更改状态都要通过mutation,mutation是同步更改的-------
this.actions = {}
forEach(actions, (fn, key) => {
this.actions[key] = (payload) => fn.call(this, this, payload);
});
// 这个状态在页面渲染时需要收集对应的渲染watcher,这样状态更新才会更新视图
this._vm = new Vue({
data: { // $符号开头的数据不会被挂载到实例上,但是会挂载到当前的_data上,减少了一次代理
$$state: state // 状态在哪里取值,就会收集对应的依赖
},
computed
});
// 用户组件中使用的$store = this
}
// 类的属性访问器
get state() { // this.$store.state => defineProperty中的get
// 依赖于 vue的响应式原理
return this._vm._data.$$state
}
dispatch = (type, payload) => { // 根据commit还是dispatch 找对应的存储结果
this.actions[type](payload)
}
commit = (type, payload) => {
this.mutations[type](payload)
}
}
modules 模块
- 将传入的options格式化成父子结构(注册)
- 给带有namespaced = true选项的选项单独存储到一个对象中
- 定义成响应式
- 如果有插件,遍历传入的方法并执行,将Store实例传入
- 严格模式: 只能内置方法修改state或者用户通过mutations修改state,否则报错(_withCommittting,利用高阶函数,修改state前将_committing表示修改为true,执行完成修改成false)
import { Vue } from './install'
import ModuleCollection from './module/module-collection';
import { forEach } from './util';
function getNewState(store, path) {
return path.reduce((memo, current) => {
return memo[current];
}, store.state)
}
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:'zf',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);
})
}
module.forEachGetter((fn, key) => {
store.wrapperGetters[ns + key] = function() {
return fn.call(store, getNewState(store, path));
}
});
module.forEachMutation((fn, key) => { // {myAge:[fn,fn]}
store.mutations[ns + key] = store.mutations[ns + key] || [];
store.mutations[ns + key].push((payload) => {
store._withCommittting(() => {
fn.call(store, getNewState(store, path), payload); // 先调用mutation 在执行subscirbe
})
store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
})
});
module.forEachAction((fn, key) => {
store.actions[ns + key] = store.actions[ns + key] || [];
store.actions[ns + key].push((payload) => {
return fn.call(store, store, payload)
})
});
module.forEachChildren((child, key) => {
installModule(store, rootState, path.concat(key), child);
});
}
function resetVM(store, state) {
let oldVm = store._vm;
store.getters = {};
const computed = {};
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 (store.strict) { // 说明是严格模式我要监控状态
store._vm.$watch(() => store._vm._data.$$state, () => {
// 我希望状态变化后 直接就能监控到,watcher都是异步的? 状态变化会立即执行,不是异步watcher
console.assert(store._committing, 'no mutate in mutation handler outside')
}, { deep: true, sync: true }); // 内部会遍历所有的属性
}
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中更改的
this.strict = options.strict;
// 没有namespace的时候 getters都放在根上 ,actions,mutations 会被合并数组
let state = options.state;
installModule(this, state, [], this._modules.root);
resetVM(this, state);
if (options.plugins) { // 说明用户使用了插件
options.plugins.forEach(plugin => plugin(this))
}
}
_withCommittting(fn) {
this._committing = true; // 如果true
fn(); // 函数是同步的 获取_commiting 就是true,如果是异步的那么就会变成false 就会打印日志
this._committing = false;
}
subscribe(fn) {
this._subscribes.push(fn);
}
replaceState(newState) { // 需要替换的状态
this._withCommittting(() => {
this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
})
// 虽然替换了状态,但是mutation getter中的state在初始化的时候 已经被绑定死了老的状态
}
get state() {
return this._vm._data.$$state
}
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))
}
registerModule(path, module) { // 最终都转换成数组 register(['a','c'])
if (typeof path == 'string') path = [path];
// module 是用户直接写的
this._modules.register(path, module); // 模块的注册, 将用户给的数据放到树中
// 注册完毕后 ,在进行安装
// 将用户的module 重新安装
installModule(this, this.state, path, module.newModule);
// vuex内部重新注册的话 会重新生成实例, 虽然重新安装了 ,只解决了状态的问题,但是computed就丢失了
resetVM(this, this.state); // 销毁重来
}
}
// state getters action mutation (modules 分层)
export default Store;
动态注册mudules
store.registerModule
registerModule(path, module) { // 最终都转换成数组 register(['a','c'])
if (typeof path == 'string') path = [path];
// module 是用户直接写的
this._modules.register(path, module); // 模块的注册, 将用户给的数据放到树中
// 注册完毕后 ,在进行安装
// 将用户的module 重新安装
installModule(this, this.state, path, module.newModule);
// vuex内部重新注册的话 会重新生成实例, 虽然重新安装了 ,只解决了状态的问题,但是computed就丢失了
resetVM(this, this.state); // 销毁重来
}
map方法
function mapState(stateList) {
let obj = {};
for (let i = 0; i < stateList.length; i++) {
let stateName = stateList[i];
obj[stateName] = function () {
return this.$store.state[stateName];
};
}
return obj;
}
function mapGetters(gettersList) {
let obj = {};
for (let i = 0; i < gettersList.length; i++) {
let getterName = gettersList[i];
obj[getterName] = function () {
return this.$store.getters[getterName];
};
}
return obj;
}
function mapMutations(mutationList) {
let obj = {};
for (let i = 0; i < mutationList.length; i++) {
obj[mutationList[i]] = function (payload) {
this.$store.commit(mutationList[i], payload);
};
}
return obj;
}
function mapActions(actionList) {
let obj = {};
for (let i = 0; i < actionList.length; i++) {
obj[actionList[i]] = function (payload) {
this.$store.dispatch(actionList[i], payload);
};
}
return obj;
}
vuex plugins
插件实现原理:
- new Store会ch查看是否有plugins选项,如果有,遍历执行所有插件并传入store
- 在编写plugins可以通过store.subscribe(fn)
- 在每次mutations执行完毕后会遍历_subscribes执行内部订阅的所有方法
- 每个fn内部回把store,修改后的state,payload传入fn中并且执行
// 编写plugin
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);
})
}
}
// 调用plugin
if (options.plugins) { // 说明用户使用了插件
options.plugins.forEach(plugin => plugin(this))
}
// 执行mutations后执行执行订阅方法(发布)
store.mutations[ns + key] = store.mutations[ns + key] || [];
store.mutations[ns + key].push((payload) => {
store._withCommittting(() => {
fn.call(store, getNewState(store, path), payload); // 先调用mutation 在执行subscirbe
})
store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
})