手写Vuex

手写Vuex

需求分析

  • Vue.use(Vuex) => 这是一个插件,需要实现一个静态方法 install
  • new Vuex.Store(options) => 需要实现一个Store类
  • 页面中可以通过 this.$store.xxx 可以访问store实例 => 需要挂载$store到Vue.prototype上
  • 页面中可以通过 this.$store.state.xxx可以动态的更新页面 => state是响应式对对象
  • 页面中可以通过 `this.$store.getters.getCounter可以动态的更新页面 => getters是响应式对对象,getters.xxx是响应式的函数取值 => 考虑computed属性
  • 实现2个方法 commit、dispatch方法

代码实现

  1. main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')
  1. store/index.js
import Vue from 'vue'
import Vuex from './KVuex'

Vue.use(Vuex)

new Vuex.Store({
  state: {
    counter: 1
  },
  mutations: {
    syncAdd(state, multiple){
      state.counter += multiple
    }
  },
  actions: {
    asyncAdd({ commit }, multiple){
      setTimeout(() => {
        commit('syncAdd',multiple)
      },300)
    }
  },
  getters: {
    getCounter: state => state.counter,
    getCounterByDivisor(state){ 
      return divisor => state.counter/ divisor
    },
    getCounter3: state => divisor => state.counter / divisor
  }
})
  1. 页面中展示
<p>{{ $store.state.counter }}</p>
<p @click="$store.commit('syncAdd(2)')"> 同步提交数据 </p>
<p @click="$store.dispatch('asyncAdd(4)')"> 异步提交数据 </p>
<p>{{ $store.getters.getCounterByDivisor(4) }}</p>
  1. 代码展示
// kVuex需求分析
// 1. new Vuex.Store() =>  实现一个Store类,而且还不能直接导出Store类,反而要导出一个对象,对象里有Store
// 2. Vue.use(Vuex) => 插件,要实现一个install方法, install 方法不是Store的静态方法,而是对象Vuex的 静态方法
// 3. vue文件中通过 this.$store 访问store实例 => 挂载$store到原型上
// 4. 在main.js中,先①引入store实例的代码,再②new Vue()传入store参数
// => ①import store时,先进行Vue.use(Vuex),即执行install方法,在该方法中进行$store挂载: this.$options.store && (Vue.prototype.$store = this.$options.store), 如果this.$options.store存在,就进行挂载。this.$options.store 这里是new Vue({store})传入的options,这个传参是在②中执行的,现在还在执行①的代码,就想把②的代码拿来用
// 解决方案:
// ① 挂载$store放在setTimeout中,延迟执行 => 代码low、延迟时间不可控
// ② 使用Vue.mixin,在beforeCreate钩子函数中进行执行挂载,在beforeCreate执行时,new Vue({store})是一定执行了的,不然进不了这个钩子函数
// 5. 页面中 this.$store.state.counter 是响应式的 =>  state是响应式数据
// 6. commit、dispatch、getter方法
// 7. 页面中 this.$store.getters.getCounter 是响应式的 => getters是响应式数据, 而且,getters里面存储的是方法,考虑使用computed计算属性来实现

let Vue;

class Store {
  constructor(options) {
    const store = this;
    this.$options = options;
    this._mutations = options.mutations;
    this._actions = options.actions;
    this._wrappedgetters = options.getters;
    this.getters = {};
    // computed : {foo(){}}没有参数的函数。而getter是有参数的
    const computed = {};
    Object.keys(this._wrappedgetters).forEach((key) => {
      computed[key] = function () {
        return store._wrappedgetters[key](store.state);
      };
      // 设置getters[key],只读属性。 并且利用computed计算属性来获取getters[key]的值,且为响应式
      Object.defineProperty(store.getters, key, {
        get() {
          return store._vm[key];
        },
      });
    });
    // 响应式数据 state
    // ① Vue.util.defineReactive => 是用来定义对象的属性为响应式
    // ② 借鸡生蛋 vue实例的data对象是响应式的
    // this.state = options.state
    // this.state = new Vue({ data: options.state }); // this.state.counter => vue实例.counter
    // 将state保护起来

    // 响应式getters是响应式、方法 => computed计算属性来实现
    // this.$store.getters.getCounter / this.$store.getters.getCounterById
    this._vm = new Vue({
      data: {
        $$state: options.state, // 在data中定义 $$xxx, 在实例中是获取不到该值的,被保护起来了。可以通过 this.$data.$$state获取
      },
      computed,
    });

    // 给commit的this指向绑定到store实例上
    // this.commit = this.commit.bind(store); 这个跟下面的函数是一个意思,只是call执行起来性能比bind好一点
    // bind、call、apply都是用来改变函数执行时的上下文的。
    // 性能(时间消耗从少到多): call > bind > apply 但是,如果我们需要传入数组,即使有es6的解构的方法,apply的性能还是要优于call/bind
    // ① bind : Function.bind(obj, arg1, arg2, arg3, ……) => 返回值是函数,需要手动调用
    // ① call : Function.call(obj, arg1, arg2, arg3, ……) => apply、call是立即调用
    // ① apply : Function.apply(obj, [argArray]) => call新能要优于apply,可能是因为apply解析数组的时候要耗费性能?
    const { commit, dispatch } = store;
    this.commit = function boundCommit(type, payload) {
      commit.call(store, type, payload);
    };

    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload);
    };
  }

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

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

  // dispatch('setCounter', payload)
  // addAsync({ commit }, payload) {
  //   setTimeout(() => {
  //     commit("addSync", payload); // 实际应用的时候是不
  //   });
  // }
  // 注意this指向问题
  dispatch(type, payload) {
    const entry = this._actions[type];
    if (entry) {
      // ① 考虑到异步操作,有可能会返回promise,进行操作,所以要return 一下
      // ② 在entry内部执行的时候,如果有setTimeout等,就会涉及到this指向问题,所以需要把 commit函数的this绑定到该store实例上。
      return entry(this, payload); // 异步
    }
  }
}

const install = function (_Vue) {
  Vue = _Vue;
  Vue.mixin({
    beforeCreate() {
      // this执行vue实例
      if (this.$options.store) {
        // 说明是根Vue,实例化中传入的参数
        Vue.prototype.$store = this.$options.store;
      }
    },
  });
};

const Vuex = {
  Store,
};

Vuex.install = install;

export default Vuex;

原文地址:https://www.cnblogs.com/shine-lovely/p/14870951.html