手摸手教你写vuex 深入了解vuex原理

92 阅读1分钟

分享之前面试遇到的一个问题:手写一个简易版的vuex

我们先来分析vuex 在main.js 引入 并通过vue.use()方法调用,知道vuex是作为一个插件使用,并且需用通过 new Vuex.Store 赋值给一个变量,所以我们得知vuex是一个类 并且有一个install方法

// main.js
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
  state:{
    
  },
  mutations: {
    
  },
  getters: {
    
  },
  actions: {
    
  }
})

所以我们新建一个store.js文件,在通过mixin混合的方式在beforeCreate周期函数把实例挂载到组件上

// store.js
// 引入vue
import Vue from 'vue'
// 声明一个vue变量 到时候把全局vue对象赋值过去
let vue

//声明Store类
class Store{
    // 构造函数
    constructor(options){
        // console.log('options', options);
    }
}

const install = (Vue) => {
    vue = Vue
    vue.mixin({
        beforeCreate(){
            // 通过mixin挂载到跟组件示例上
            if (this.$options && this.$options.store){
                this.$store = this.$options.store
            } else {
                // 否则去父组件找
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}

export default {
    Store, install
}

我们可以在constructor 函数里面打印看看options是什么:

image.png

就是main.js 里面的store对象,因此我们就可以根据options参数来组装我们需要的数据

首先是state对象

由于我们vuex的数据是响应式的数据 所以我们借助vue的响应式来接受state数据

image.png

但是此时会发现state被多嵌套了一层 需要this.$store.vm.state.xxx才能调用

所以我们借助类的get 方法把整个对象返回出去

image.png

整理后代码:

// store.js
// 引入vue
import Vue from 'vue'
// 声明一个vue变量 到时候把全局vue对象赋值过去
let vue

//声明Store类
class Store{
    // 构造函数
    constructor(options){
        // console.log('options', options);
        this.vm = new Vue({
           data:{
               state: options.state
           }
        })
    }
    get state() {
      return this.vm.state
  }
}

const install = (Vue) => {
    vue = Vue
    vue.mixin({
        beforeCreate(){
            // 通过mixin挂载到跟组件示例上
            if (this.$options && this.$options.store){
                this.$store = this.$options.store
            } else {
                // 否则去父组件找
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}

export default {
    Store, install
}

接着 getters

拿到getters对象,通过Object.keys() 拿到对象的key

由于getters需要响应式,所以借助js的 Object.defineProperty方法来实现

image.png

接下来是 mutations

image.png

在实际应用我们是通过comit来提交到mutation 并通过payload 作为参数,所以应该在类里面声明一个commit函数

// commit for mutatiuons
  commit(type,payload) {
    this.mutations[type](payload)
  }

action同理

image.png

action方法是通过dispatch 触发,所以类中:

image.png

最后整理完整代码:

// store.js
import Vue from "vue";
let vue;
class Store {
  constructor(options){
    // console.log('options', options);
    this.vm = new Vue({
      data:{
        state: options.state
      }
    })
    // getters
    const getters = options.getters
    this.getters = {}
    Object.keys(getters).forEach((getterName) => {
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return getters[getterName](this.state)
        }
      })
    })

    // mutations
    const mutations = options.mutations
    this.mutations = {}
    Object.keys(mutations).forEach((mutationName) => {
      this.mutations[mutationName] = (payload) => {
        mutations[mutationName](this.state, payload)
      }
    })

    // actions
    const actions = options.actions
    this.actions = {}
    Object.keys(actions).forEach( actionName => {
      this.actions[actionName] = (payload) => {
        actions[actionName](this, payload)
      }
    })
  }
  // commit for mutatiuons
  commit(type,payload) {
    this.mutations[type](payload)
  }

  // dispatch for actions
  dispatch(type, payload)  {
    this.actions[type](payload)
  }
  get state() {
    return this.vm.state
  }
}
const install = (_Vue) => {
  vue = _Vue
  vue.mixin({
    beforeCreate() {
      // 通过mixi挂载到根组件示例
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        this.$store = this.$parent && this.$parent.$store
      }
    },
  })
}
export default {
  Store,
  install
}