浅析vuex的实现原理

714 阅读5分钟

vuex是vue中的状态管理器。可以使我们轻松解决在开发中遇到的多组件状态同步等问题, 同时通过store对vue的数据统一管理,可以使数据流向变得清晰、可追踪、可预测。对于一些中大型的应用,vuex的使用可以大大提升项目的稳定性和扩展性!

vuex的构成

vuex本质是一个对象,其中分别定义了state getters mutations actions 四个属性来管理vue中的数据。其中store和getters用于定义状态。使用 mutation 和 action对状态进行变更;引入module对状态进行模块化分割;引入插件对状态进行快照、记录、以及追踪等;提供了mapState、mapGetters、 mapActions、 mapMutations 辅助函数方便开发者在vm中处理store。

vuex的核心原理

  • vuex暴露一个Store类和一个install方法。
  • store注入 vue的实例组件的方式,是通过vue的 mixin机制,借助vue组件的生命周期 钩子 beforeCreate 完成的。即 每个vue组件实例化过程中,会在 beforeCreate 钩子前调用 vuexInit 方法
  • 通过new Vue({ data: data }), 将store中的state包装成一个data。实现state中数据的响应式。

vuex简单实现

1.首先通过vue-cli 创建一个vue项目,不选择引入vuex。在项目的根目录创建文件夹myVuex。 新建store.js 里边包含两个方法,分别是State类和install方法。

// store.js
export let Vue;
import applyMixin from './mixin.js';

export class Store {
    
} 

export const install = () => {
    
}

install方法中通过Vue.mixin() 方法将store全局混入。新建mixin.js,并且修改store.js如下

// mixin.js
export default function applyMixin (Vue) {
  Vue.mixin({
    beforeCreate: VuexInit
  })
}


function VuexInit () {
  const options = this.$options;
  if (options.store) { // 只有根组件实例上绑定了store属性
    this.$store = options.store;
  } else if (options.parent && options.parent.$store) { // 子组件
    this.$store = options.parent.$store;
  }
}

// store.js
export const install = (_vue) => {
  console.log('--------------------'); // 使用Vue.use会调用install方法
  Vue = _vue;
  applyMixin(Vue); // 通过mixin绑定
}

2.完成全局注入变量我们就可以确保在每一个vue的组件实例上都能拿到this.$store属性。然后我们来分别实现state、getters、mutations、actions四个属性。 首先我们先看下使用vuex时怎样定义store的

import Vue from 'vue';
import Vuex from '../vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    name: 'aaaaa',
    age: 1,
  },
  getters: {
    myName: state => state.name + 'sasdsds',
    myAge: state => state.age,
  },
  mutations: {
    add(state, payload) {
      state.age += payload;
    },
    setName(state, payload) {
      state.name = payload;
    }
  },
  actions: {
    add({ commit }, payload) {
      console.log('commit', commit);
      commit('add', payload);
    }
  }
})

也就是整个store中定义的状态都作为参数传入Store类中。Store类中接受一个参数options。所以接下来我们分别实现。

  • state

定义一个state属性接收options中的state参数。然后通过初始化实例new Vue({ data: this.state}),将state传入vue实例的data中实现state中变量的响应式。同时我们给Store类添加一个state属性。这个属性自动触发get接口,类似Object.defineProperty({get()})。这样我们就可以通过this.$state.store拿到对应的store了。

// store.js
export class Store {
    constructor(options) {
    console.log(options);
    // --------------------state--------------------------------------
    // stare默认值是一个对象, 通过new Vue({ data: data }) 将数据绑定在
    this.state = options.state || {};
    this._vm = new Vue({
      data: this.state,
    })
  }
  
 get State() { // 属性访问器   new Store().state  Object.defineProperty({get()})
    console.log('get state');
    return this._vm.data;
 }

} 

使用引入vuex的方式引入我们自己的Myvuex,在组件中通过this.$store.state打印可以看到结果: 这样,我们就成功实现了vuex中state。

  • getters

我们通过遍历获取所有的getters方法,当我们获取getters的时候会通过Object.defineProperty() 方法获取get接口。这样我们才可以在使用getters的时候不加括号。

// store.js
export class Store {
    constructor(options) {
    // --------------------state--------------------------------------
    // stare默认值是一个对象, 通过new Vue({ data: data }) 将数据绑定在
    this.state = options.state || {};
    this._vm = new Vue({
      data: this.state,
    })
    
     // --------------------=getters--------------------------------------
    let getters = options.getters || {}; // getters中可能会有多个方法
    this.getters = {};
    // 遍历整个对象,拿到每一个getter
    Object.keys(getters).forEach((getter) => {
      Object.defineProperty(this.getters, getter, {
        // 当你要获取getter的时候,会自动调用get这个方法
        //一定要用箭头函数,要不然this指向会出现问题
        get: () => {
          return getters[getter](this.state);
        }
      });
    });
    
  }
  
  
 get State() { // 属性访问器   new Store().state  Object.defineProperty({get()})
    console.log('get state');
    return this._vm.data;
 }

} 
  • mutations mutations跟getters一样,还是用mutations对象将用户传入的mutations存储起来。但是mutations必须通过commit()方法去提交状态。所以,在Store中还需要增加一个comit方法。
// store.js
export class Store {
    constructor(options) {
    // --------------------state--------------------------------------
    // stare默认值是一个对象, 通过new Vue({ data: data }) 将数据绑定在
    this.state = options.state || {};
    this._vm = new Vue({
      data: this.state,
    })
    
     // --------------------=getters--------------------------------------
    let getters = options.getters || {}; // getters中可能会有多个方法
    this.getters = {};
    // 遍历整个对象,拿到每一个getter
    Object.keys(getters).forEach((getter) => {
      Object.defineProperty(this.getters, getter, {
        // 当你要获取getter的时候,会自动调用get这个方法
        //一定要用箭头函数,要不然this指向会出现问题
        get: () => {
          return getters[getter](this.state);
        }
      });
    });

    // --------------------mutations--------------------------------------
    let mutations = options.mutations || {};
    this.mutations = {}
    Object.keys(mutations).forEach((mutation) => {
      this.mutations[mutation] = (payload) => {
        // 传递上下文和负载
        mutations[mutation](this.state, payload);
      }
    });
    
  }
  
  commit = (type, payload) => {
    //把方法名和参数传给mutations
    // 使用箭头函数,防止this指向拿不到。
    this.mutations[type](payload);
  }
  
  
 get State() { // 属性访问器   new Store().state  Object.defineProperty({get()})
    console.log('get state');
    return this._vm.data;
 }

} 
  • actions 实现了mutations,那么actions就比较简单了。同样是保存传递进来的actions,同时添加一个dispatch()方法来提交action。
// store.js
export class Store {
    constructor(options) {
    // --------------------state--------------------------------------
    // stare默认值是一个对象, 通过new Vue({ data: data }) 将数据绑定在
    this.state = options.state || {};
    this._vm = new Vue({
      data: this.state,
    })
    
     // --------------------=getters--------------------------------------
    let getters = options.getters || {}; // getters中可能会有多个方法
    this.getters = {};
    // 遍历整个对象,拿到每一个getter
    Object.keys(getters).forEach((getter) => {
      Object.defineProperty(this.getters, getter, {
        // 当你要获取getter的时候,会自动调用get这个方法
        //一定要用箭头函数,要不然this指向会出现问题
        get: () => {
          return getters[getter](this.state);
        }
      });
    });

    // --------------------mutations--------------------------------------
    let mutations = options.mutations || {};
    this.mutations = {}
    Object.keys(mutations).forEach((mutation) => {
      this.mutations[mutation] = (payload) => {
        // 传递上下文和负载
        mutations[mutation](this.state, payload);
      }
    });
    // --------------------action--------------------------------------
    let actions = options.actions || {};
    console.log(actions, 'actions');
    this.actions = {}
    // 这里传递this到actions中,所以在actions中可以拿到commit() 方法和 state。
    // 使用action的时候 ,其中的{ commit } 就是对this的解构
    // add({ commit }, payload) {
    //   console.log('commit', commit);
    //   commit('add', payload);
    // }
    Object.keys(actions).forEach((action) => {
      this.actions[action] = (payload) => {
        actions[action](this, payload);
      }
    })
  }
  
  
  dispatch = (type, payload) => {
    this.actions[type](payload);
  }

  
  commit = (type, payload) => {
    //把方法名和参数传给mutations
    // 使用箭头函数,防止this指向拿不到。
    this.mutations[type](payload);
  }
  
  
 get State() { // 属性访问器   new Store().state  Object.defineProperty({get()})
    console.log('get state');
    return this._vm.data;
 }

} 

结语

本文参考了网上一些大佬的博文,加上自己的理解。仅实现了vuex中基本的状态。但是通过自己手动实践,也对vuex的使用有了全新的了解。