vuex原理以及实现

vuex官方文档vue

Vuex是什么?

Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

每个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有如下两点不一样:git

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地获得高效更新。
  2. 你不能直接改变 store 中的状态。改变 store 中的状态的惟一途径就是显式地提交 (commit) mutation。这样使得咱们能够方便地跟踪每个状态的变化,从而让咱们可以实现一些工具帮助咱们更好地了解咱们的应用。

实现简易版的vuex

先来看下vuex的基本用法

import Vue from 'vue';  
import Vuex from 'vuex';  
// 1.Vue.use(Vuex);  Vuex是一个对象 install方法  
// 2.Vuex中有一个Store类 // 3.混入到组件中 增添store属性  
  
Vue.use(Vuex); // 使用这个插件  内部会调用Vuex中的install方法  
  
const store = new Vuex.Store({  
 state:{ // -> data  
 age:10  
 }, getters:{ // 计算属性  
 myAge(state){  
 return state.age + 20 } }, mutations:{ // method=> 同步的更改state  mutation的参数是状态  
 changeAge(state,payload){  
 state.age += payload; // 更新age属性  
 } }, actions:{ // 异步操做作完后将结果提交给mutations  
 changeAge({commit},payload){  
 setTimeout(() => {  
 commit('changeAge',payload) }, 1000); } }});  
export default store;

经过用法能够知道:算法

  1. Vuex是一个对象,它做为vue的插件,必然有install方法;
  2. Vuex中有一个Store类,在使用的时候有使用new;
  3. 须要将store混入到组件中。

因而能够梳理好入口文件 vuex

vuex/index.js设计模式

import { Store, install } from './store';   
// 这个文件是入口文件,核心就是导出全部写好的方法  
export default {  
 Store, install  
}

store文件

vuex/store.jsapi

export let Vue;  
  
export class Store {  
  }  
  
// _vue 是Vue的构造函数  
export const install = (_vue) => { // 须要保存Vue,用户传入的Vue构造函数  
 Vue = _vue; }

接下来就是把store挂载到每一个组件上面,这样数据才能互通共享,很显然,经过Vue.mixin 在Vue生命周期beforeCreate 能够为每一个组件注入store;数组

import applyMixin from "./mixin";export let Vue;  
  
export class Store {  
  }  
  
// _vue 是Vue的构造函数  
export const install = (_vue) => { // 须要保存Vue,用户传入的Vue构造函数  
 Vue = _vue; // 须要将根组件中注入的store 分派给每个组件 (子组件) Vue.mixin applyMixin(Vue);  
}

vuex/mixin.js缓存

export default function applyMixin(Vue) {  
 // 父子组件的beforecreate执行顺序  
 Vue.mixin({ // 内部会把生命周期函数 拍平成一个数组   
beforeCreate: vuexInit  
 });}  
  
// 组件渲染时从父=》子  
  
function vuexInit() {  
 // 给全部的组件增长$store 属性 指向咱们建立的store实例  
 const options = this.$options; // 获取用户全部的选项  
 if (options.store) { // 根实例(只有根实例才会有store属性)  
 this.$store = options.store;  
 } else if (options.parent && options.parent.$store) { // 儿子 或者孙子....  
 // 后面的每个都从其父组件拿到store  
 this.$store = options.parent.$store;  
 }}

接下来就是处理state,getters,mutations,actionsapp

state实现

export class Store {  
 constructor(options) {  
 const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
 this._vm = new Vue({  
 data: { // 属性若是是经过$开头的 默认不会将这个属性挂载到vm上  
 $$store: state  
 } }) } get state() { // 属性访问器 new Store().state  Object.defineProperty({get()}) return this._vm._data.$$state  
 }  }

首先来处理state,options是用户传入的,其中有state,getters,mutations,actions,天然能够在options.state中取到,可是此时state还不是响应式,能够借助new Vue中data的数据是响应式处理这个问题,将state挂载到$$state上,这个属性是不会被vue暴露出去(多是内部作了处理)。当咱们在组件中去获取值的时候,好比this.$store.state.age时候 this.$store.state 就走到到了访问器get state() 就会将整个仓库的state返回出去,并且数据是响应式的。至于为何在_vm._data上,须要去看下vue源码实现。异步

getters实现

export class Store {  
 constructor(options) {  
 // 1.处理state  
 const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
 this._vm = new Vue({  
 data: { // 属性若是是经过$开头的 默认不会将这个属性挂载到vm上  
 $$store: state  
 } })     // 2.处理getters属性 具备缓存的 computed 带有缓存 (屡次取值是若是值不变是不会从新取值)  
 this.getters = {}; Object.key(options.getters).forEach(key => {  
 Object.defineProperty(this.getters, key, {  
 get: () => options.getters[key](this.state)  
 }) }) } get state() { // 属性访问器 new Store().state  Object.defineProperty({get()}) return this._vm._data.$$state  
 }  
}

经过循环用户传进来的getters,再经过Object.defineProperty把每个getter放入store中。不过目前每一次取值都会从新计算,没有缓存功能,不符合vue计算属性的用法以及定义。

先来改造下对象遍历这个方法,由于这个方法后面用的比较多。

vuex/util.js

export const forEachValue = (obj, callback) => { Object.keys(obj).forEach(key => callback(obj[key], key))  
}
export class Store {  
 constructor(options) {  
 // 1.处理state  
 const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
 this._vm = new Vue({  
 data: { // 属性若是是经过$开头的 默认不会将这个属性挂载到vm上  
 $$store: state  
 } })  
 // 2.处理getters属性 具备缓存的 computed 带有缓存 (屡次取值是若是值不变是不会从新取值)  
 this.getters = {}; forEachValue(options.getters, (fn, key) => {  
 Object.defineProperty(this.getters, key, {  
 get: () => fn(this.state)  
 }) }) } get state() { // 属性访问器 new Store().state  Object.defineProperty({get()}) return this._vm._data.$$state  
 }  
}

逻辑都是同样的,接着处理下缓存功能。

export class Store {  
 constructor(options) {  
 // 1.处理state  
 const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
 const computed = {};  
 // 2.处理getters属性 具备缓存的 computed 带有缓存 (屡次取值是若是值不变是不会从新取值)  
 this.getters = {}; forEachValue(options.getters, (fn, key) => {  
 // 将用户的getters 定义在实例上, 计算属性是如何实现缓存  
 computed[key] = () => fn(this.state);  
 // 当取值的时候执行计算属性的逻辑,此时就有缓存功能  
 Object.defineProperty(this.getters, key, {  
 get: () => fn(this._vm[key])  
 }) })  
 this._vm = new Vue({  
 data: { // 属性若是是经过$开头的 默认不会将这个属性挂载到vm上  
 $$store: state  
 }, computed, })  
 } get state() { // 属性访问器 new Store().state  Object.defineProperty({get()}) return this._vm._data.$$state  
 }  
}

computed具备缓存功能,能够在用户传入的getters的时候,将用户的getters 定义在实例上,computed[key] = () => fn(this.state) ,在取值的时候fn(this._vm[key])执行计算属性的逻辑。vuex的做者真是脑洞大开,鬼才啊,这都能想到。

mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)

  • 对传入的属性进行遍历订阅
  • 经过commit方法触发调用。

mutation实现

// 3.实现mutations  
this.mutations = {};forEachValue(options.mutations, (fn, key) => {  
 this.mutations[key] = (payload) => fn(this.state, payload)  
})  
  
commit = (type, payload) => { //保证当前this 当前store实例  
 this.mutations[type](payload)  
}
commit使用箭头函数是为了保证调用的都是当前实例,一是经过this.commit(type,data),二是在action中被解构使用changeAge({commit},payload){}

actions和dispath也是如此。

完整的Store类

export class Store {  
 constructor(options) {  
 // 1.处理state  
 const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
 const computed = {};  
 // 2.处理getters属性 具备缓存的 computed 带有缓存 (屡次取值是若是值不变是不会从新取值)  
 this.getters = {}; forEachValue(options.getters, (fn, key) => {  
 // 将用户的getters 定义在实例上, 计算属性是如何实现缓存  
 computed[key] = () => fn(this.state);  
 // 当取值的时候执行计算属性的逻辑,此时就有缓存功能  
 Object.defineProperty(this.getters, key, {  
 get: () => fn(this._vm[key])  
 }) })  
 this._vm = new Vue({  
 data: { // 属性若是是经过$开头的 默认不会将这个属性挂载到vm上  
 $$store: state  
 }, computed, })  
 // 3.实现mutations  
 this.mutations = {}; forEachValue(options.mutations, (fn, key) => {  
 this.mutations[key] = (payload) => fn(this.state, payload)  
 })  
 // 4.实现actions  
 this.actions = {}; forEachValue(options.actions, (fn, key) => {  
 this.actions[key] = (payload) => fn(this, payload);  
 });  
 }  
 commit = (type, payload) => { //保证当前this 当前store实例  
 this.mutations[type](payload)  
 } dispatch = (type, payload) => { this.mutations[type](payload)  
 }  
 get state() { // 属性访问器 new Store().state  Object.defineProperty({get()}) return this._vm._data.$$state  
 }  
}

完整的store.js

import applyMixin from "./mixin";import { forEachValue } from './util';export let Vue;  
  
export class Store {  
 constructor(options) {  
 // 1.处理state  
 const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
 const computed = {};  
 // 2.处理getters属性 具备缓存的 computed 带有缓存 (屡次取值是若是值不变是不会从新取值)  
 this.getters = {}; forEachValue(options.getters, (fn, key) => {  
 // 将用户的getters 定义在实例上, 计算属性是如何实现缓存  
 computed[key] = () => fn(this.state);  
 // 当取值的时候执行计算属性的逻辑,此时就有缓存功能  
 Object.defineProperty(this.getters, key, {  
 get: () => fn(this._vm[key])  
 }) })  
 this._vm = new Vue({  
 data: { // 属性若是是经过$开头的 默认不会将这个属性挂载到vm上  
 $$store: state  
 }, computed, })  
 // 3.实现mutations  
 this.mutations = {}; forEachValue(options.mutations, (fn, key) => {  
 this.mutations[key] = (payload) => fn(this.state, payload)  
 })  
 // 4.实现actions  
 this.actions = {}; forEachValue(options.actions, (fn, key) => {  
 this.actions[key] = (payload) => fn(this, payload);  
 });  
 }  
 commit = (type, payload) => { //保证当前this 当前store实例  
 this.mutations[type](payload)  
 } dispatch = (type, payload) => { this.mutations[type](payload)  
 }  
 get state() { // 属性访问器 new Store().state  Object.defineProperty({get()}) return this._vm._data.$$state  
 }  
}  
  
// _vue 是Vue的构造函数  
export const install = (_vue) => { // 须要保存Vue,用户传入的Vue构造函数  
 Vue = _vue; // 须要将根组件中注入的store 分派给每个组件 (子组件) Vue.mixin applyMixin(Vue);  
}

简易版的vuex到此完成。接下来就是要处理module。

完整版Vuex实现

咱们实现了一个简易版的Vuex,对state,actions,mutations,getters 进行了功能的实现。可是没有对modules进行处理,其实modules才是Vuex中最核心而且是最难实现的。

Vuex 容许咱们将 store 分割成大大小小的对象,每一个对象也都拥有本身的 state、getter、mutation、action,这个对象咱们把它叫作 module(模块),在模块中还能够继续嵌套子模块。
  • state: 全部模块中的state中数据最终都会嵌套在一棵树上。相似于以下

  • 模块内部的 action、mutation 和 getter 默承认是注册在全局命名空间的,这样使得多个模块可以对同一 mutation 或 action 做出响应。所以在订阅mutation 和action时必须存储在数组中,每次触发,数组中的方法都要执行。

Vuex中能够为每一个模块添加namespaced: true来标记为当前模块划分一个命名空间,接下来看下具体怎么实现一个完整的Vuex。

具体实现

整体思路能够分为如下:

  1. 模块收集。就是把用户传给store的数据进行格式化,格式化成咱们想要的结构(树)
  2. 安装模块。须要将子模块经过模块名定义在跟模块上
  3. 把状态state和getters定义到当前的vm上。

模块收集

import ModuleCollection from './module/module-collection'  
export let Vue;  
  
export class Store {  
 constructor(options) {  
     const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)  
  
 // 1.模块收集  
 this._modules = new ModuleCollection(options);  
 }}

ModuleCollection 类的实现

这个类是收集模块,格式化数据用的,那咱们先要知道须要什么样的格式。

this.root = { _raw: '根模块',  
 _children:{  
 a:{  
 _raw:"a模块",  
 _children:{  
 c:{  
 ..... } }, state:'a的状态'    
        },  
 b:{  
 _raw:"b模块",  
 _children:{},  
 state:'b的状态'    
        }  
 }, state:'根模块本身的状态'  
 }```  
  
最终须要的是这样一个数结构。

export default class ModuleCollection {
constructor(options) {
// 注册模块 须要用到栈结构数据,[根,a],每次循环递归的时候将其入栈。这样每一个模块能够清楚的知道本身的父级是谁
this.register([], options)
}
register(path, rootModule) {
// 格式化后的结果
let newModule = {_raw: rootModule, // 用户定义的模块
_children: {}, // 模块的儿子
state: {} // 当前模块的状态
}

if (path.length === 0) { // 说明是根模块

this.root = newModule }
// 用户在模块中传了modules属性
if (rootModule.modules) {
// 循环模块 module模块的定义 moduleName模块的名字
forEachValue(rootModule.modules, (module, moduleName) => {
this.register(path.concat(moduleName), module)
}) } }
}

第一次进来的时候path是空数组,root就是用户传进去的模块对象;若是模块有modules属性,须要循环去注册这个模块。path.concat(moduleName) 就返回了[a,c]相似的格式。 接下来看下path不为空的时候

if (path.length === 0) { // 说明是根模块
this.root = newModule} else {
// this.register(path.concat(moduleName), module); 递归注册前会把module 的名放在 path的位
this.root._children[path[path.length -1]] = newModule}

path[path.length -1] 能够取到最后一项,也就是模块的儿子模块。这里咱们用的是this.root._children[path[path.length -1]] = newModule。这样写会把有多层路径的模块最后一项也提到和它平级,所以须要肯定这个模块的父级是谁,再把当前模块挂到父级就okl了

if (path.length === 0) { // 说明是根模块
this.root = newModule} else {
// this.register(path.concat(moduleName), module); 递归注册前会把module 的名放在 path的位
// path.splice(0, -1) 是最后一项,是须要被挂的模块
let parent = path.splice(0, -1).reduce((memo, current) => {
return memo._children[current];
}, this.root);
parent._children[path[path.length - 1]] = newModule}

### 模块的安装  
  
将全部module收集后须要对收集到数据进行整理  
  
- state数据要合并。 **经过Vue.set(parent,path[path.length-1],rootModule.state),既能够合并,又能使使 module数据成为响应式数据;**  
- action 和mutation 中方法订阅(数组)

// 1.模块收集
this._modules = new ModuleCollection(options);

// 2.安装模块 根模块的状态中 要将子模块经过模块名 定义在根模块上
installModule(this, state, [], this._modules.root);

this就是store, 须要完成installModule方法。installModule中传入的有当前模块,这个模块可能有本身的方法。为此先改造下代码,建立Module类。

import { forEachValue } from '../util';

class Module {
get namespaced() {
return !!this._raw.namespaced
}
constructor(newModule) {
this._raw = newModule; this._children = {}; this.state = newModule.state
}
getChild(key) {
return this._children[key];
}
addChild(key, module) {
this._children[key] = module
}
// 给模块继续扩展方法
}

export default Module;

ModuleCollection中相应的地方稍做修改。

import Module from './module'

export default class ModuleCollection {
constructor(options) {
// 注册模块 须要用到栈结构数据,[根,a],每次循环递归的时候将其入栈。这样每一个模块能够清楚的知道本身的父级是谁
this.register([], options)
}
register(path, rootModule) {
// 格式化后的结果
let newModule = new Module(rootModule)
if (path.length === 0) { // 说明是根模块
this.root = newModule } else { // this.register(path.concat(moduleName), module); 递归注册前会把module 的名放在 path的位
// path.splice(0, -1) 是最后一项,是须要被挂的模块
let parent = path.splice(0, -1).reduce((memo, current) => {
return memo.getChild(current);
}, this.root);
parent.addChild(path[path.length - 1], newModule) }
// 用户在模块中传了modules属性
if (rootModule.modules) {
// 循环模块 module模块的定义 moduleName模块的名字
forEachValue(rootModule.modules, (module, moduleName) => {
this.register(path.concat(moduleName), module)
}) }
}}

function installModule(store, rootState, path, module) {
// 这里我须要遍历当前模块上的 actions、mutation、getters 都把他定义在store的_actions, _mutations, _wrappedGetters 中
}

installModule 就须要循环对当前模块处理对应的actions、mutation、getters。为此能够对Module类增长方法,来让其内部本身处理。

import { forEachValue } from '../util';

class Module {

constructor(newModule) {
this._raw = newModule; this._children = {}; this.state = newModule.state
}
getChild(key) {
return this._children[key];
}
addChild(key, module) {
this._children[key] = module
}
// 给模块继续扩展方法
forEachMutation(fn) {
if (this._raw.mutations) {
forEachValue(this._raw.mutations, fn)
} }
forEachAction(fn) {
if (this._raw.actions) {
forEachValue(this._raw.actions, fn);
} }
forEachGetter(fn) {
if (this._raw.getters) {
forEachValue(this._raw.getters, fn);
} }
forEachChild(fn) {
forEachValue(this._children, fn);
}}

export default Module;

function installModule(store, rootState, path, module) {
// 这里我须要遍历当前模块上的 actions、mutation、getters 都把他定义在store的_actions, _mutations, _wrappedGetters 中

// 处理mutation
module.forEachMutation((mutation, key) => {
store._mutations[key] = (store._mutations[key] || [])
store._mutations[key].push((payload) => {
mutation.call(store, module.state, payload)
}) })
// 处理action
module.forEachAction((action, key) => {
store._actions[key] = (store._actions[key] || [])
store._actions[key].push((payload) => {
action.call(store, store, payload)
}) })
// 处理getter
module.forEachGetter((getter, key) => {
store._wrappedGetters[key] = function() {
return getter(module.state)
} })
// 处理children
module.forEachChild((child, key) => {
// 递归加载
installModule(store, rootState, path.concat(key), child)
})
}

此时,已经把每一个模块的actions、mutation、getters都挂到了store上,接下来须要对state处理。

// 将全部的子模块的状态安装到父模块的状态上
// 须要注意的是vuex 能够动态的添加模块
if (path.length > 0) { let parent = path.slice(0, -1).reduce((memo, current) => {
return memo[current] }, rootState) // 若是这个对象自己不是响应式的 那么Vue.set 就至关于 obj[属性 ]= 值
Vue.set(parent, path[path.length - 1], module.state);}

到此已经完成模块的安装,接下里是要把这些放到Vue实例上面  
  
### 模块与实例的关联

constructor(options) {

const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)
this._mutations = {}; this._actions = {}; this._wrappedGetters = {};
// 1.模块收集
this._modules = new ModuleCollection(options);

// 2.安装模块 根模块的状态中 要将子模块经过模块名 定义在根模块上
installModule(this, state, [], this._modules.root);

// 3,将状态和getters 都定义在当前的vm上
resetStoreVM(this, state);

}

function resetStoreVM(store, state) {
const computed = {}; // 定义计算属性
store.getters = {}; // 定义store中的getters
forEachValue(store._wrappedGetters, (fn, key) => {
computed[key] = () => { return fn(); } Object.defineProperty(store.getters, key, {
get: () => store._vm[key] // 去计算属性中取值
}); }) store._vm = new Vue({
data: {
$$state: state
}, computed // 计算属性有缓存效果
});}

相对应的Store类作如下修改

export class Store {
constructor(options) {

const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)
this._mutations = {}; this._actions = {}; this._wrappedGetters = {};
// 1.模块收集
this._modules = new ModuleCollection(options);
// 2.安装模块 根模块的状态中 要将子模块经过模块名 定义在根模块上
installModule(this, state, [], this._modules.root);

// 3,将状态和getters 都定义在当前的vm上
resetStoreVM(this, state);

}
commit = (type, payload) => { //保证当前this 当前store实例
this._mutations[type].forEach(mutation => mutation.call(this, payload))
}
dispatch = (type, payload) => { this._actions[type].forEach(action => action.call(this, payload))
}
get state() { // 属性访问器 new Store().state Object.defineProperty({get()}) return this._vm._data.$$state
}
}

### 命名空间nameSpaced  
  
默认状况下,模块内部的 action、mutation 和 getter 是注册在**全局命名空间**的——这样使得多个模块可以对同一 mutation 或 action 做出响应。  
  
若是但愿你的模块具备更高的封装度和复用性,你能够经过添加 `namespaced: true` 的方式使其成为带命名空间的模块。当模块被注册后,它的全部 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。  
  
![](https://gitee.com/yutao618/images/raw/master/images/170ade78c9e40e00.png)  
  
日常写上面基本上都要加上 namespaced,防止命名冲突,方法重复屡次执行。如今就算每一个 modules 的方法命同样,也默认回加上这个方法别包围的全部父结点的 key,核心就是 **path** 变量,在安装模块的时候把path处理下:

// 我要给当前订阅的事件 增长一个命名空间
let namespace = store._modules.getNamespaced(path); // 返回前缀便可

**store._modules**就是模块收集好的模块,给它增长一个获取命名空间的方法。  
  
给ModuleCollection类增长一个getNamespaced方法,其参数就是path。

// 获取命名空间, 返回一个字符串
getNamespaced(path) {
let root = this.root; // 从根模块找起来
return path.reduce((str, key) => { // [a,c]
root = root.getChild(key); // 不停的去找当前的模块
return str + (root.namespaced ? key + '/' : '') }, ''); // 参数就是一个字符串
}

固然Module类也须要增长一个属性访问器

get namespaced() {
return !!this._raw.namespaced
}

接下来就是在处理mutation,action,getters的时候key的值加上namespace就能够了。

// 处理mutation
module.forEachMutation((mutation, key) => {
store._mutations[namespace + key] = (store._mutations[namespace + key] || [])
store._mutations[namespace + key].push((payload) => {
mutation.call(store, module.state, payload)
})})

// 处理action
module.forEachAction((action, key) => {
store._actions[namespace + key] = (store._actions[namespace + key] || [])
store._actions[namespace + key].push((payload) => {
action.call(store, store, payload)
})})

// 处理getter
module.forEachGetter((getter, key) => {
store._wrappedGetters[namespace + key] = function() {
return getter(module.state) }})

namespaces 核心就是对数据格式的处理,来进行发布与订阅。  
  
### 插件  
  
> Vuex 的 store 接受 `plugins` 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 做为惟一参数  
  
使用的时候:

const store = new Vuex.Store({
// ... plugins: [myPlugin]
})

在插件中不容许直接修改状态——相似于组件,只能经过提交 mutation 来触发变化  
  
先来看下一个vuex本地持久化的一个插件

function persists() {
return function(store) { // store是当前默认传递的
let data = localStorage.getItem('VUEX:STATE');
if (data) { store.replaceState(JSON.parse(data));
} store.subscribe((mutation, state) => {
localStorage.setItem('VUEX:STATE', JSON.stringify(state));
}) }}

插件返回一个函数,函数的参数就是store。其中replaceState, subscribe是关键点,也是vuex其中的2个api,接下来实现一下这2个方法。

export class Store {
constructor(options) {

const state = options.state; //数据变化要更新视图 (vue的核心逻辑依赖收集)
this._mutations = {}; this._actions = {}; this._wrappedGetters = {};
// 1.模块收集
this._modules = new ModuleCollection(options);
// 2.安装模块 根模块的状态中 要将子模块经过模块名 定义在根模块上
installModule(this, state, [], this._modules.root);

// 3,将状态和getters 都定义在当前的vm上
resetStoreVM(this, state);

// 插件内部会依次执行
options.plugins.forEach(plugin=>plugin(this));

}
commit = (type, payload) => { //保证当前this 当前store实例
this._mutations[type].forEach(mutation => mutation.call(this, payload))
}
dispatch = (type, payload) => { this._actions[type].forEach(action => action.call(this, payload))
}
get state() { // 属性访问器 new Store().state Object.defineProperty({get()}) return this._vm._data.$$state
}
}

options.plugins.forEach(plugin=>plugin(this))就是让全部插件依次执行,参数就是**store**.

this._subscribes = [];// ...
subscribe(fn){
this._subscribes.push(fn);
}

subscribe就介绍一个函数,放入到一个数组或者队列中去。

// 处理mutation
module.forEachMutation((mutation, key) => {
store._mutations[namespace + key] = (store._mutations[namespace + key] || [])
store._mutations[namespace + key].push((payload) => {
mutation.call(store, module.state, payload)
store._subscribes.forEach(fn => {
fn(mutation, rootState) }) })})

相应的在安装模块处理mutation的时候,须要让订阅的store._subscribes执行。fn的参数就是mutation和根状态。

replaceState(state){
// 替换掉最新的状态
this._vm._data.$$state = state
}

这是最简单的改变状态的方法,但此时虽然是ok的,可是mutation提交的仍是旧值,mutation.call(store, module.state, payload)这个地方仍是有点问题,module.state拿到的不是最新的状态。

function getState(store, path) { // 获取最新的状态 能够保证视图更新
return path.reduce((newState, current) => {
return newState[current]; }, store.state);
}

能够经过这个方法能获取到最新的转态,相应的在处理mutation,getters的地方作相应调整。

// 处理mutation
module.forEachMutation((mutation, key) => {
store._mutations[namespace + key] = (store._mutations[namespace + key] || [])
store._mutations[namespace + key].push((payload) => {
mutation.call(store, getState(store, path), payload)
store._subscribes.forEach(fn => {
fn(mutation, store.state)
}) })})

// 处理getter
module.forEachGetter((getter, key) => {
store._wrappedGetters[namespace + key] = function() {
return getter(getState(store, path))
}})

以前的mutation.state所有替换成getState去获取最新的值。n(mutation, rootState) 也替换为fn(mutation, store.state),这样就能够了。固然源码中并无getState去或获取最新状态的方法。  
  
### Vuex中的辅助方法  
  
> 所谓辅助函数,就是辅助咱们平时使用,说白了就是让咱们偷懒。  
  
咱们在页面组件中可能会这样使用

<template>
<div id="app"> 个人年龄是:{{this.$store.getters.age}}
<button @click="$store.commit('changeAge',5)">同步更新age</button>
<button @click="$store.commit('b/changeAge',10)">异步更新age</button>
</div></template>
<script>

export default {
computed: {
}, mounted() {
console.log(this.$store);
},};
</script>

this.$store.getters.age这样用固然是能够,可是就是有点啰嗦,咱们能够作如下精简

computed:{
age() {
return this.$store.getters.age
}}

this.$store.getters.age 直接替换成 age,效果确定是同样的。可是写了在computed中写了age方法,感受仍是啰嗦麻烦,那再来简化一下吧,先看下用法:

computed:{
...mapState(['age'])
}

mapState实现

export function mapState(stateArr) {
let obj = {}; for (let i = 0; i < stateArr.length; i++) {
let stateName = stateArr[i]; obj[stateName] = function() { return this.$store.state[stateName]
} } return obj}

那如法炮制,mapGetters

export function mapGetters(gettersArr) {
let obj = {}; for (let i = 0; i < gettersArr.length; i++) {
let gettName = gettersArr[i]; obj[gettName] = function() { return this.$store.getters[gettName]
} } return obj}

mapMutations

export function mapMutations(obj) {
let res = {}; Object.entries(obj).forEach(([key, value]) => {
res[key] = function (...args) { this.$store.commit(value, ...args)
} }) return res;}

mapActions

export function mapActions(obj) {
let res = {}; Object.entries(obj).forEach(([key, value]) => {
res[key] = function (...args) { this.$store.dispatch(value, ...args)
} }) return res;}

其中这些方法都是在一个helpers文件中。在vuex/index文件中将其导入。

import { Store, install } from './store';
// 这个文件是入口文件,核心就是导出全部写好的方法
export default {
Store, install
}

export * from './helpers';

### createNamespacedHelpers  
  
能够经过使用 `createNamespacedHelpers` 建立基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。

export const createNamespacedHelpers = (namespace) => ({ mapState: mapState.bind(null, namespace),
mapGetters: mapGetters.bind(null, namespace),
mapMutations: mapMutations.bind(null, namespace),
mapActions: mapActions.bind(null, namespace)
})

## 总结  
  
vuex的核心功能基本是完成,也能实现基本功能,不过看源码对不少细节作了处理,边界作了判断。并且其中用到 了不少设计模式以及不少技巧和算法。
相关文章
相关标签/搜索