99行代码实现简易版Vuex

145 阅读1分钟

丢到浏览器直接运行

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简易版Vuex</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
</head>

<body>
    <div id="app">{{$store.state.count}} {{$store.getters.newCount}}</div>
    <script>
        class Store {
            constructor(options) {
                let state = options.state;
                this.getters = {};
                this.mutations = {};
                this.actions = {}
                this.state = Vue.observable(options.state) //变成响应试数据
                if (options.getters) {
                    forEach(options.getters, (getterName, getterFn) => {
                        // 循环getters里面的每一项数据 给getters中的每一项添加响应式 并执行里面的函数, 只要在模板中取值时 以来的state改变 那么getter自然改变
                        Object.defineProperty(this.getters, getterName, {
                            get: () => {
                                // 和computed实现原理一样
                                return getterFn(state);
                            }
                        })
                    });
                }
                forEach(options.mutations, (mutationName, mutationFn) => {
                    // 包裹函数 等待commit()函数调用
                    this.mutations[mutationName] = () => {
                        // 改变this指向,指向store实例
                        mutationFn.call(this, state);
                    }
                });
                // 包裹函数 等待dispatch()函数调用函数调用
                forEach(options.actions, (actionName, actionFn) => {
                    this.actions[actionName] = () => {
                        // 改变this指向 使其store实例
                        actionFn.call(this, this);
                    }
                });
                //切片编程 把原型上的方法存起来,内部调用 防止action时this丢失
                let { commit, dispatch } = this;
                this.commit = (type) => {
                    commit.call(this, type);
                }
                this.dispatch = (type) => {
                    dispatch.call(this, type);
                }
            }
            commit(type) {
                this.mutations[type]()
            }
            dispatch(type) {
                this.actions[type]()
            }
        }
        const forEach = (obj, callback) => {
            Object.keys(obj).forEach(item => callback(item, obj[item]));
        }
        const install = (Vue) => {
            Vue.mixin({
                beforeCreate() {
                    // 根组件中增加$store属性
                    if (this.$options && this.$options.store) { //是否是根组件
                        this.$store = this.$options.store;
                    } else { // 子组件增加$store属性
                        this.$store = this.$parent && this.$parent.$store
                    }
                }
            })
        }
        const Vuex = {
            install: install,
            Store: Store
        }
        Vue.use(Vuex); // install 方法

        const store = new Vuex.Store({
            state: {
                count: 100
            },
            getters: {
                newCount(state) {   // 200
                    return state.count + 100;
                }
            },
            mutations: {
                change(state) {
                    state.count += 10
                }
            },
            actions: {
                change({ commit }) {
                    setTimeout(() => {
                        // 此时会去调用原型上的commit, 但是commit方法上会丢失this,this此时指向undefined
                        commit('change');
                    }, 1000);
                }
            }
        })
        new Vue({
            el: '#app',
            store,
            mounted() {
                this.$store.dispatch('change')
            }
        })
    </script>
</body>

</html>