实现一个简易版vuex

1,326 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

介绍

近来被问到不使用vuex,那你可以自己实现一个vuex吗?

以前是经常使用vuex,但是让自己实现一个vuex,还真没有考虑到。于是先去查阅了一下资料,学习一下,然后自己来实现一个简易版的vuex。

接下来就一步一步实现一个简易版vuex

创建工程

用vue-cli脚手架快速创建一个基础工程

创建一个plugin

平时我们使用vuex的时候,是先导入vuex,然后使用Vue.use(vuex)来注册这个插件。然后我们就可以使用vuex了。

我们自己实现一个简易版的vuex,也可以借鉴这种思路,以vue插件的形式来实现,开发一个vue的插件

  1. 先在src目录下创建一个文件夹--plugin

  2. 在plugin里再创建一个文件夹-myStore

  3. 在myStore里,先创建一个store,js文件,在里面定义Store类

    3.1 constructor

    constructor({ state, getters, mutations, actions }) {
            this.state = Vue.observable(state || {})
            this.getters = {}
            this.mutations = mutations || {}
            this.actions = actions || {}
    ​
            const _getters = getters || {}
            for (let fn in _getters) {
                Object.defineProperty(this.getters, fn, {
                    get() {
                        return _getters[fn].call(_self, _self.state)
                    },
                })
            }
        }
    

    在constructor里,把传递进来的参数保存起来

    • 用Vue.observable 来创建一个响应式对象。(这点比较重要,vuex里的数据改变了,视图要跟着刷新,主要是用这个响应式对象来处理,即vue的双向数据绑定)

    • 用Object.defienProperty 来定义getters里的属性 访问器

      达到的效果是,访问getters里的属性,相当于执行getters里属性同名的方法,我们在vue里面就可以直接访问属性,而不是调用方法

      
      this.$store.getters.age
      

    3.2 commit 调用mutations的方法

    
    commit(cb, ...args) {
            _self.mutations[cb].apply(_self, [_self.state, ...args])
        }
    

    3.3 dispath 调用actions的方法

    
    dispath(cb, ...args) {
            _self.actions[cb].apply(_self, [_self, ...args])
    }
    

    3.4 install

    Vue.use() 会调用插件暴露出来的install方法

    
    static install(_Vue, storeName = "store") {
            Vue = _Vue
            Vue.mixin({
                beforeCreate() {
                    let store = this.$options[storeName]
                    if (store) {
                        Vue.prototype[`$${storeName}`] = store
                        _self = store
                    }
                },
            })
        }
    

    install里主要是用Vue.mixin在beforeCreate里混入一些逻辑。从$options里拿出store,然后挂载到Vue的原型上。这样每个Vue实例都可以访问到store

    我这里是直接导出这个Store类,所以把install放到类方法里。但是不一定要这么做,只要在导出的对象里,有一个install方法即可

    
    export default Store
    
  4. 在myStore里创建一个index.js文件,定义自己store里的数据

    4.1 引入Vue,引入刚定义Store,通过Vue.ues 注册这个插件

    
    import Vue from "vue"
    import Store from "./store"Vue.use(Store, "myStore")
    

    4.2 new一个Store实例,定义自己的数据,然后导出

    
    export default new Store({
        state: {
            number: 1,
        },
        mutations: {
            addNumber(state) {
                state.number++
            },
            subNumber(state) {
                state.number--
            },
        },
        actions: {
            addNumberAsync(context) {
                context.commit("addNumber")
            },
        },
        getters: {
            getNumber(state) {
                return state.number + 10
            },
        },
    })
    

引入store

在main.js里导入我们刚才导出的store实例,然后挂载到根实例上


import myStore from "./plugin/myStore/index"new Vue({
    myStore,
    render: (h) => h(App),
}).$mount("#app")

这里挂载到根实例,是为了在install方法里,从$options里拿到这个store实例,从而完成数据传递

使用store

引入store后,接下来就可以在组件里面使用store里。和我们使用vuex时,使用的方式一样,因为定义的属性和方法都是一样的


<template>
  <div>
    <div>{{ $myStore.state.number }}</div>
    <button @click="add">增加</button>
    <button @click="sub">减少</button>
    <button @click="addAsync">异步增加</button>
    <button @click="clickGetters">获取getters</button>
  </div>
</template>
<script>
export default {
  methods: {
    add() {
      this.$myStore.commit('addNumber')
    },
    sub() {
      this.$myStore.commit('subNumber')
    },
    addAsync() {
      this.$myStore.dispath('addNumberAsync')
    },
    clickGetters() {
      console.log(this.$myStore.getters.getNumber)
    },
  },
}
</script>

思维导图

实现简易版vuex.jpg

小结

至此,整个vuex基本实现了,不过是一个简易版的vuex,有些细节没有实现。但还是完成了