vuex学习之实现一个简单的集中式存储插件

211 阅读2分钟

作为一个学习者,应该要有记录笔记的习惯,和村长学习了vuex的简易实现方式,过两天就忘了岂不糟糕?

首先在测试项目中直接把vuex替换为自己写的简易版fvuex插件作为测试用例

// 1. 简单配置下store下面的index.js中的配置项
export default new Vuex.Store({
    state: {
        counter: 0
    },
    getters: {
        doubleCounter: state => state.counter * 2
    },
    mutations: {
        add (state) {
            state.counter++
        }
    },
    actions: {
        add ({ commit }) {
            setTimeout(() => {
                commit('add')
            }, 1000)
        }
    }
})

// 2. 在App.vue中写入测试代码,保证在使用正常的Vuex插件中可以正常反馈效果
<div @click="$store.commit('add')">counter: {{$store.state.counter}}</div>
<div @click="$store.dispatch('add')">async counter: {{$store.state.counter}}</div>
<div @click="$store.getters.doubleCounter++">doubleCounter: {{$store.getters.doubleCounter}}</div>

// 把vuex插件修改为简易版fvuex插件
// 3. 复制一份store文件夹,并命名为fstore,在该文件夹下创建一个fstore.js作为插件
// 然后在fstore下面的index.js中引入fstore.js并使用该插件,导出不变
import Vuex from './fstore.js'
Vue.use(Vuex)

// 4.在main.js中修改引入的store,并将之挂载到Vue实例中
import store from './fstore'
new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

实现方式

创建js插件

  • 实现Store类
    • 实现一个响应式的state
    • 实现getters派生对象
    • 实现commit方法
    • 实现dispatch异步方法
  • 实现install方法挂载$store
  • 导出Vuex对象,包含Store类与install方法
// fstore.js
let Vue; // 声明Vue变量方便全局使用
class Store {
    constructor (options = {}) {
        // 声明computed变量
        const computed = {}
        const store = this // 将this指向保存到store中
        // 保存用户配置的getters选项
        // {doubleCounter(state){}}
        this.getters = options.getters || {}
        Object.keys(this.getters).forEach(key => {
            // 获取用户定义的getter,它是一个无参数的函数
            const fn = store.getters[key]
            // 将有参数的函数转换为computed可以使用的无参数形式
            computed[key] = function () {
                return fn(store.state)
            }
            Object.defineProperty(this.getters, key, {
                get: () => {
                    // 此处必须用箭头函数来使this指向store实例
                    // computed中的属性可以被vue实例直接访问到
                    return store._vm[key]
                },
                set: () => {
                    console.error('请用commit方法修改state中的属性。')
                }
            })
        })
        // 通过vue的实例化中的data()把state变成响应式的属性
        this._vm = new Vue({
            data () {
                return {
                    $$state: options.state // 隐藏state变量保存在类内部的_vm中的_data中的$$state中
                    // 防止用户直接通过this.state.xx访问或this.state.xx++修改
                    // 只允许用户通过单一的state方式访问和commit方法修改
                }
            },
            computed
        })
        // 保存用户配置的mutations选项
        this._mutations = options.mutations || {}
        // 保存用户配置的actions选项
        this._actions = options.actions || {}
        // 并绑定commit和dispatch上下文,防止用户在setTimeout中调用这些方法时this指向出错
        const {commit, dispatch} = store
        store.commit = (type, payload) => {
            commit.call(store, type, payload)
        }
        store.dispatch = (type, payload) => {
            dispatch.call(store, type, payload)
        }
    }
    
    get state () {
        // 用户在此入口访问state中的属性
        return this._vm._data.$$state
    }
    set state (value) {
        // 不允许用户直接通过state.xx修改属性值
        console.error('请用commit方法修改state中的属性。')
    }
    commit (type, payload) {
        const entry = this._mutations[type]
        if (!entry) {
            return console.error(`unknown mutation type: ${type}`)
        }
        entry(this.state, payload)
    }
    dispatch (type, payload) {
        const entry = this._actions[type]
        if (!entry) {
            return console.error(`unknown action type: ${type}`)
        }
        // 此处要传Store的实例作为dispatch的上下文
        // 异步方法要return 一个 promise
        return entry(this, payload)
    }
}

function install (_Vue) {
    // 将_Vue构造方法保存到Vue变量中
    Vue = _Vue;
    Vue.mixin({
        beforeCreate () {
            // 此处的this指的是组件实例
            if (this.$options.store) {
                Vue.prototype.$store = this.$options.store
            }
        }
    })
}

export default { Store, install }