Vuex原理浅析

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的完整代码了,有错误的地方请大家指正。

原文链接:juejin.im

上一篇:Vue的学习笔记一:Vue简介
下一篇:写一个支持Vue响应式的localStorage插件

相关推荐

  • (干货👍)从详细操作js数组到浅析v8中array.js

    前言 最近在写面试编程题,经常用到数组,经常想偷个懒,用它提供的方法,奈何还是对数组方法使用不熟练,导致写了很多的垃圾代码,很多地方稍加修改的话肯定变得简洁高效优雅👊 所以✍这篇文章本着了解一下Ja...

    3 个月前
  • 骚操作!在react中使用vuex

    原文地址 前言 笔者最近在学习使用react,提到react就绕不过去redux。redux是一个状态管理架构,被广泛用于react项目中,但是redux并不是专为react而生,两者还需要react...

    2 年前
  • 页面刷新后,vuex中数据丢失、清空的解决方案 vuex-persistedstate

    场景之一 应用API进行用户身份验证,将登录状态保存为Vuex状态中的布尔值。 当用户登录时,设置了 登录状态 并相应地有条件地显示 登录/注销 按钮。 ​ 但是当刷新页面时,vue应用程序的状态将...

    1 年前
  • 重探浏览器事件(浅析事件编程化)

    前言 在平常开发过程中,就算不使用现在主流的框架也至少得使用个Jquery,这些工具帮我们统一不同浏览器平台之间的差异和细节,可以将注意力集中到开发上来. 不过有意思的一点是,在看完高程的N年后我居然...

    1 年前
  • 速览vuex源码

    Vuex 源码不过千行,主要使用了 Store 类、ModuleCollection 类、Module 类,结构清晰,下面简单说说 Vuex 中一些主要的源码实现。推荐打开 Vuex 源码一同观看。

    5 个月前
  • 通过简易项目浅析vue的生命周期并理解MVVM(不仅理论,图文并茂)

    文章写作提示, 文章还没有写完。。。 1.v-if 在组件上和组件根元素上的生命周期 2.附带vue-table 3.vue-cli构建的项目组件切换时的生命周期问题最后提问 开始前说一说 吐槽 vu...

    3 年前
  • 逐行粒度的vuex源码分析

    了解vuex 什么是vuex vuex是一个为vue进行统一状态管理的状态管理器,主要分为state, getters, mutations, actions几个部分, vue组件基于state进行...

    2 年前
  • 跟我一起从0到1编写一个自己的Vuex

    前言 在前端工程化开发的今天,vuex、redux成为了我们项目中状态管理的上上之选。关于如何使用它,相信这已经成为前端开发者的必备技能之一了。今天,我们来一起尝试进阶一下,自己实现一个状态管理器来...

    8 个月前
  • 谈谈数据状态管理和实现一个简易版vuex

    本文在github做了收录 github.com/Michael-lzg… 掘金同文:https://juejin.im/post/6866964944634511368数据状态管理首先说说什么叫“...

    1 个月前
  • 记录vuex module 模块化分割

    参考官网例子,加深下vuex的学习。 随着项目的复杂度增大,为了方便管理vuex,一般会将其按功能分割成不同的模块,方便日后管理,先看下整体的目录结构: 目录结构 store里面暂时弄了common...

    2 年前

官方社区

扫码加入 JavaScript 社区