实现简单的vuex

46 阅读1分钟
  • main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
import store from './store'

new Vue({
  store: store,
  render: h => h(App),
}).$mount('#app')

  • store.js
import Vuex from '../vuex'
import Vue from 'vue'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 1
  },
  getters: {
    doubleCount: state => {
      return state.count * 2
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    incrementAsync (context) {
      setTimeout(() => {
        context.commit('increment')
      }, 1000);
    }
  }
})
  • 手写的vuex.js
let Vue;
class Store {
  /**
   * this 指向 store实例
   * @param {*} options  new Vuex.Store中的参数选项
   */
  constructor(options) {
    // 让state里面的属性响应式
    this.state = new Vue({
      data: options.state
    })
    this.mutations = options.mutations
    this.actions = options.actions

    // 固定this指向
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)

    this.getters = {}
    if (options.getters) {
      this.handleGetters(options.getters)
    }
  }
  /**
   * $store.getters.doubleCount 会自动执行对应的 getters对应的方法
   * @param {*} getters 
   */
  handleGetters (getters) {
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        // $store.getters.doubleCount 会执行get
        get: () => {
          return getters[key](this.state)
        }
      })
    })
  }

  /**
   * $store.commit('increment') 根据 fnKey 找到 mutations 中的方法,并执行时传入state
   * @param {*} fnKey 方法名
   * @param {*} payload 参数
   */
  commit (fnKey, payload) {
    const mutationsFn = this.mutations[fnKey]
    mutationsFn(this.state)
  }

  /**
   * $store.dispatch('incrementAsync') 根据 fnKey 找到 actions 中的方法,并执行时传入this
   * @param {*} fnKey 
   * @param {*} payload 
   */
  dispatch (fnKey, payload) {
    const actionsFn = this.actions[fnKey];
    actionsFn(this, payload)
  }
}


/**
 *  Vue.use(Vuex) 时调用install方法, 并传入Vue
 *  this.$store 访问的就是当前实例
 * @param {*} _Vue 
 */
const install = (_Vue) => {
  Vue = _Vue;
  Vue.mixin({
    beforeCreate () {
      if (this.$options.store) {
        // main.js中 选项中的 store实例
        Vue.prototype.$store = this.$options.store
      }

    },
  })
}

export default {
  install, Store
}
  • App.vue 验证
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <p>count:{{$store.state.count}}</p>
    <p>doubleCount:{{$store.getters.doubleCount}}</p>
    <button @click="$store.commit('increment')">setCount</button>
    <button @click="$store.dispatch('incrementAsync')">setCountAsync</button>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {
  }
}
</script>
<style>
</style>