简单的Vuex实现

218 阅读1分钟

1 从Vuex的结构分析

我们先从Vuex的基本结构来分析

0d55c12acf89b7c2b361cde4c042403b.png

基本上我们就是在页面中通过state访问数据,然后通过commitdispatch方法来更新数据。而当我们改变state中的数据时也会通知页面重新渲染,这个过程其实还是通过Vue来实现的。当我们的state是响应式数据时,数据更新就会通知页面进行更新。

所以,我们要做的就有一个大致的思路了。我们实现一个Store类,类中有一个响应式的state,然后有commit和dispatch方法。而响应的state其实我们可以通过new Vue({data: state})


2 Vuex基本结构实现

接下我们就能来着手实现一下

// 存储Vue构造函数,避免直接引用Vue
let Vue

class Store {
  constructor(options) {
    //将state变为响应式数据,数据变化时直接通知页面更新
    this.state = new Vue({
      data: options.state
    })
  }
}

function install(_Vue) {
  Vue = _Vue

  // 通过混入寻找根实例进行全局挂载
  Vue.mixin({
    beforeCreate() {
      if(this.$options.store) {
        Vue.prototype.$store = this.$options.store
      }
    },
  })
}


export default {
  Store,
  install
}

这里new Vue的data写法是因为在mian.js中才会将我们的store初始化,这里直接就是根实例,之前我们用data(){ reutrn }就是避免组件的data对全局进行污染,然而我们这里是根实例,不用考虑。

基本的state已经实现了,接下来我们就可以来着手实现两个方法了。

我们先从commit来,commit提交的mutations里的方法,所以我们要先获取到mutations,然后我们根据传入的方法名来找到对应的方法进行调用。

constructor(options) {
  this._mutations = options.mutations
}

commit(type, payload) {
  const entry = this._mutations[type]
  if(entry) {
    entry(this.state, payload)
  }
}

dispatch也差不多

dispatch(type, payload) {
  const entry = this._actions[type]
  if(entry) {
    entry(this, payload)
  }
}

因为dispatch第一个参数接收的是一个store上下文,所以我们这里直接传一个this。

我们试着用一下。

import Vue from 'vue'
import Vuex from './cvuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    counter: 0
  },
  getters: {
    doubleCounter(state) {
      return state.counter * 2
    }
  },
  mutations: {
    add(state) {
      state.counter++
      // this.state
    }
  },
  actions: {
    // 结构上下文
    add({ commit }) {
      setTimeout(() => {
        commit('add')
      }, 1000);
    }
  }
})

63870f08b41ecd469861aa647b199b4a.png

这里报错显示_mutations未定义,那么就是说我们在commit中const entry = this._mutations[type]这一步出现了问题。而这里大概率出问题的地方就在于this的指向。


3 actions中this指向问题

我们来看看我们在actions中是怎么定义方法的。

actions: {
  // 解构上下文
  add({ commit }) {
    setTimeout(() => {
      commit('add')
    }, 1000);
  }
},

有没有发现,我们这里是直接解构commit并进行了commit的独立调用。我们知道函数的独立调用在非严格模式下this为window,而在严格模式下的this则为undefined。不管哪个模式,我们都无法在commit中通过this拿到mutations中的方法。

所以我们这里有两种解决方案。

  1. 不使用独立函数调用:

    add(context) {
      setTimeout(() => {
        contextcommit('add')
      }, 1000);
    }
    

    不结构,直接拿到我们传入的this,进行隐式绑定拿到this

  2. 通过显示绑定

    constructor(options) {
      ...
      this.commit = this.commit.bind(this)
      this.dispatch = this.dispatch.bind(this)
    }
    

    这样不管我们怎么调用这两个方法,我们都能拿到this。


4 隐藏state

现在就大致实现了一个vuex,但是Vuex不允许我们直接对state进行赋值。但是我们现在的cvuex是可以直接用$store.state.a = 1这种方式直接进行赋值。所以我们要进行 一点优化。

constructor(options) {
  // this.state = new Vue({
  //   data: options.state
  // })
  this._vm = new Vue({
    data: {
      $$state: options.state
    }
  })
}

get state() {
  console.log(this._vm);
  return this._vm._data.$$state
}

在vue的data中我们用两个$定义的参数是不能用一般的方法进行访问。然后我们在写一个存取器返回state。


5 getters实现

我们好像还忘了getters的实现。

我们先从getters的使用来分析。

<p>double counter:{{$store.getters.doubleCounter}}</p>

在我们使用时,getters是直接在$store下获取的,所以,我们的getters应该是直接定义在构造器里面的而不是单独的方法。

我们再从定义来看

getters: {
  doubleCounter(state) {
    return state.counter * 2
  }
}

我们定义的时候是直接定义不同的方法来获取。所以我们要做的就是在state中添加一个getters属性,但是我们用.xxx获取的时候需要调用这个方法并传入我们的state来获取返回值,换句话说就是需要将getters中的属性方法变为属性值。

这个时候我们就需要用上vue中的计算属性。

constructor(options) {
  const getters = options.getters;
  this.getters = {};
  const computed = {};

  for (const key in getters) {
    if (Object.hasOwnProperty.call(getters, key)) {
      const element = getters[key];
      // 将每个属性方法都变为computed中的属性方法,这样就能直接通过计算属性获取属性值
      computed[key] = () => {
        return element(this.state)
      }
    }
  }

  this._vm = new Vue({
    data: {
      $$state: options.state,
    },
    computed,
  });
}

现在我们就能通过创建的Vue实例直接访问属性来获取属性值。然后通过Object.defineProperty来重新设置对象属性。

for (const key in getters) {
  if (Object.hasOwnProperty.call(getters, key)) {
    const element = getters[key];
    computed[key] = () => {
      return element(this.state)
    }
  }

  // 通过设置get直接获取vue实例中计算属性的值,进而将属性方法变为属性值
  Object.defineProperty(this.getters, key, {
    get: () => {
      return this._vm[key]
    }
  })
}

6b19f263904b31d141a59862e24563c8.png