vuex小轮子

206 阅读1分钟

主要实现

  1. vuex也是一个插件
  2. 实现四个东西:state/mutations/actions/getters
  3. 创建Store
  4. 数据响应式(基于vue)

那么接下来就开始写代码咯

iVuex.js

let Vue;

class Store {
    constructor(options){
        this.state = new Vue({
            data: options.state
        });
        this.mutations = options.mutations;
        this.actions = options.actions;

        options.getters && this.handleGetters(options.getters)
    }

    // 因为commit 要做提交更新数据操作,会有作用域问题,所以用箭头函数,让 commit 一直指向 Store
    // 注意这里用箭头函数形式,后面actions实现时会有作用
    commit = (type, arg) => {
        this.mutations[type](this.state, arg);
    };

    dispatch(type, arg) {
        this.actions[type]({
            commit: this.commit,
            state: this.state
        }, arg);
    }

    handleGetters(getters) {
        // 定义this.getters
        this.getters = {};
        // 遍历getters选项,为this.getters定义property
        // 属性名就是选项中的key,只需定义get函数保证其只读性
        Object.keys(getters).forEach(key => {
             // defineProperty 给对象指定一个属性(可以控制只读)
            Object.defineProperty(this.getters, key, {
                // 在这里就是给 this.getters 对象指定若干属性 key,内容是 {}
                // 里面有一个 get 函数(只读)
                get: () => { // 注意依然是箭头函数
                    return getters[key](this.state); // 这就是调用传进来到函数
                }
            });
        });
    }
}

function install(_Vue) {
    // 这样store执行的时候,就有了Vue,不用import
    // 这也是为啥Vue.use必须在新建store之前
    Vue = _Vue;
    
    // 混入
    Vue.mixin({
        beforeCreate() {
            // 这样才能获取到传递进来的store
            // 只有root元素才有store,所以判断一下
            if(this.$options.store){
                Vue.prototype.$store = this.$options.store;
            }
        }
    })
}

export default { Store, install}

好了接下来试试到底行不行呢?

iStore.js

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

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state, n = 1) {
            state.count += n;
        }
    },
    // 派生属性
    getters: {
        score(state) {
            return `共扔出:${state.count}`;
        }
    },
    actions: {
        incrementAsync({ commit }) {
            setTimeout(() => {
                commit("increment", 5);
            }, 1000)
            // dispatch("....")
        }
    }
});

main.js

import store from './iStore'

Home.vue

<template>
  <div id="app">
    <div>冲啊,手榴弹扔了{{$store.state.count}}个</div>
    <button @click="addAsync">蓄力扔俩</button>
  </div>
</template>
<script>
  export default {
    methods: {
      addAsync() {
        this.$store.dispatch("incrementAsync");
      }
    }
  };
</script>