Vuex原理浅析

679 阅读5分钟

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

状态管理

通常我们可以这样来使用

new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template: `
    <div>{{ count }}</div>
  `,
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})

这个状态自管理应用包含以下几个部分:

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入导致的状态变化。`

vuex初始化

vuex的核心就是定义了一个store(容器)来管理应用的的数据状态,与单纯的全局对象数据存储相比,vuex的功能更强大。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。 **在项目中使用vuex**相信下面的代码,用vue开发过项目的人都很熟悉
import Vue from 'vue'
import Vuex from 'vuex'
// 这里会执行vuex的install方法 
Vue.use(Vuex);

export default new Vuex.store({
    state: { // 统一的状态管理
    age:10,
    a:100
  },
  getters:{
    // 计算属性
    myAge(state){ // object.defineProperty
      return state.age + 18;
    }
  },
  mutations: { // 可以更改状态
    syncAdd(state,payload){ // $store.commit()
      state.age += payload;
    },
    syncMinus(state,payload){
      state.age -= payload

    }
  },
  actions: { // 异步提交更改
    asyncMinus({commit},payload){ // action 异步获取完后 提交到mutation中
      setTimeout(()=>{
        commit('syncMinus',payload);
      },1000)
    }
  }
})

这样我们就可以在项目中集中管理公用的状态了。那么vuex是怎么实现这一切的呢?vuex的入口提供的install方法就可以完成初始化工作

const install = (_vue) => {
   //vue.use 传入vue的构造函数
    Vue = _vue;
    // 给vue的组件实例都混入一个钩子函数
    Vue.mixin({
        beforeCreate() {
            // 首先 store是放在根组件上,我们需要获取,并给每个组件挂上 即 this.$store = store
            if(this.$options && this.$options.store) {
                // 给根实例增加$store属性
                this.$store = this.$options.store
            }else{
                // 其他组件只需从它的父级获取
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}
// store类
class Store{
    
}

export default {
    install,
    Store
}

核心store

vuex的初始化工作很简单,我们在 import Vuex 之后,会实例化其中的 Store 对象,返回 store 实例并传入 new Vue 的 options 中,也就是我们刚才提到的 options.store。Store 对象的构造函数接收一个对象参数,它包含 actions、getters、state、mutations、modules 等 Vuex 的核心概念。

// 迭代对象的 会将对象的 key 和value 拿到
const forEach = (obj,cb)=>{ 
    Object.keys(obj).forEach(key=>{
        cb(key,obj[key]);
    })
}
class Store {
    constructor(options={}) {
         // 将用户的状态放到了store中  定义了响应式变化 数据更新 更新视图
        this.s = new Vue({
            data() {
                return {
                    state: options.state
                }
            }
        })
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        // 计算属性
        forEach(options.getters,(getterName,fn)=>{
            Object.defineProperty(this.getters,getterName,{
                get() {
                    return fn(this.state)
                }
            })
        })
        //mutations
        forEach(options.mutations,(mutationName,fn)=>{
            this.mutations[mutationName] = (payload)=>{
                // 内部的第一个参数是状态
                fn(this.state,payload)
            }
        })
        //actions
        forEach(options.actions,(actionName,fn)=>{
            this.actions[actionName] = (payload)=>{
                fn(this,payload)
            }
        })
    }
    get state(){ // 类的属性访问器
        return this.s.state
    }
    //提交更改 找到对应的mutations函数执行
    commit = (mutationName,payload)=>{
        this.mutations[mutationName](payload)
    }
    dispatch = (actionName,payload)=>{
        this.actions[actionName](payload)
    }
}

到此,一个简陋的vuex状态管理就实现了,但是把所有的状态都放到store存储,那么这个对象会变的十分的庞大,为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

vuex的模块是默认是没有强制命名空间的,除了state按模块区分了,如果模块中的matutions和actions和主对象有同名,那么他们会一起执行。

class Store {
    constructor(options = {}){
         this.s = new Vue({ 
            data(){
                return {state:options.state}
            }
        });
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        // 模块把数据格式化成一个有层次的树状结构,方面后面解析
        this._modules = new ModuleCollection(options); 
    }
    get state() {
        return this.s.state
    }
}

从数据结构上来看,模块的设计就是一个树型结构,store 本身可以理解为一个 root module,它下面的 modules 就是子模块,Vuex 需要完成这颗树的构建

// 模块的数据结构
let root = {
    _raw:options,
    _chidlren:{
        a:{
            _raw:{},
            _chidlren:{},
            state:{a:1}
        },
        b:{}
    },
    state:options.state
}
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 {
            // 如果是modules 里面的内容
            //  _children 属性 找到上一级路径
            let parent = path.slice(0,-1).reduce((root,current)=>{
                return root._children[current] 
            },this.root)
            parent._children[path[path.length - 1]] = module
        }
        //看当前模块是否有modules 
        if(rootModule.modules) {
            forEach(rootModule.modules,(moduleName, module) => {
                this.register(path.concat(moduleName),module)
            })
        }
    }
}

ModuleCollection 实例化的过程就是执行了 register 方法,在源码中register有3个参数,其中 path 表示路径,因为我们整体目标是要构建一颗模块树,path 是在构建树的过程中维护的路径;rawModule 表示定义模块的原始配置,另一个是runtime表示是否是一个运行时创建的模块,这里不影响。

初始化模块后,我们就有了一个模块的树状结构,我们就可以对对模块中的 state、getters、mutations、actions 做初始化工作,把它们和store对象连接起来。

/*
rootState:表示根上面的状态
path: 模块路径
rootMudule: 当前模块对象内容
*/
const installModule = (store, rootState, path, rootModule) => {
    if(path.length > 0) {
        let parent = path.slice(0,-1).reduce((root,currnet)=>{
            return root[current]
        },rootSate)
        // 找到模块对应得状态,按路径 挂在 store.state
        Vue.set(parent,path[path.lenght-1],rootModule.state)
    }
    // 处理  模块中 getters actions mutation
    let getters = rootModule._rawModule.getters;
    if(getters) {
        forEach(getters,(getterName,fn)=>{
            Object.defineProperty(store.getters,getterName,{
                get() {
                    // 让getter执行当自己的状态 传入
                    return fn(rootModule.state); // 让对应的函数执行
                }
            })
        })
    }
    let mutations = rootModule._rawModule.mutations;
    if (mutations) {
        forEach(mutations, (mutationName, fn) => {
            let mutations = store.mutations[mutationName] || [];
            // 默认情况下,没有强制命名空间,所以 同名得mutations 会一起执行
            mutations.push((payload) => {
                fn(rootModule.state, payload);
            })
            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;
        })
    }
    // 处理_children里面得module
    forEach(rootModule._chidlren,(moduleName, module) => {
        installModule(store, rootState, path.concat(moduleName), module)
    })
}

有了installModule这个处理模块数据得方法后,我们再改下Store的代码:

class Store {
    constructor(options = {}) {
        // 将用户的状态放到了store中
        this.s = new Vue({ // 核心 定义了响应式变化 数据更新 更新视图
            data() {
                return { state: options.state }
            }
        }); // 用来维护全局数据的
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        this._subscribes = [];
        this._modules = new ModuleCollection(options); // 把数据格式化成一个 想要的树结构
        // this._modules.root 从根模块开始安装
        installModule(this, this.state, [], this._modules.root);

    }
    // 提交更改 会在当前的 store上 找到对应的函数执行
    commit = (mutationName, payload) => { // 保证this
        this.mutations[mutationName].forEach(fn => fn(payload))
    }
    dispatch = (actionName, payload) => {
        this.actions[actionName](payload); //源码里有一个变量 来控制是否是通过mutation 来更新状态的
    }
    get state() { // 类的属性访问器
        return this.s.state
    }
}

以上就是一个简易vuex的完整代码了,有错误的地方请大家指正。