从理解源码开始学习Vuex(一)

Vuex

前言

9012年了,牛客的面经看的笔者由衷得以为,这年头,没看过源码,估计都不敢说本身是前端攻城狮了吧,再不折腾一下,估计连切图都要轮不上了。虽说安心作个切图仔仍是挺快乐的,不过说实在的,没有梦想的切图仔,和咸鱼有什么分别。javascript

今天笔者要讨论的就是如何实现一个缩小版的Vuex,本篇先你们熟悉一下他的内部结构大概是什么样的以及实现一个本身的stategettersmutationsactionsmodules部分笔者将在下一篇文章中进行介绍。前端

准备工做

用了这么久Vuex了,咱们不妨大胆的想象几个问题:vue

  • 咱们是怎么在每一个Vue实例上都能拿到$store,它的实现原理是什么。
  • Vuex 怎么实现和Vue同样,数据改变,界面自动更新。
  • gettersmutationsactions有什么不一样,怎么实现。
  • mutationsactions有什么不一样,应用场景的区别,为何。

接下来,咱们将从这几个问题开始,揭开Vuex的神秘面纱java

Vuex解析

Vuex试探之旅,你值得拥(fang)有(qi)。git

友情提醒,下面的代码不能直接用,只是笔者截取了片断用于理解,文末会贴上完整代码。es6

由于本篇不涉及modules,因此文中gettersmutationsactions实现将会和源码有一些的出入,不过,原理是同样的。github

如何给每一个实例注入$store

若是你们有本身注册导入过Vuex,那么相信你们对这样的过程天然是了然于心:vuex

  • 先得心应手的import Vuex from 'vue'
  • 而后面无表情的Vue.use(Vuex)
  • 接着略带得意的const store=new Vuex.Store({...})
  • 最后如释重负的在new Vue的地方放上store

咱们从第二个步骤开始研究。异步

至于那些问import怎么用的大哥我只能默默地说一句,9012年了,没接触过es6你是怎么学完Vue.js的。函数

顾名思义,use就是用的意思,这其实就涉及到Vue的这一个插件安装机制了,它会默认去找须要安装的模块的install方法,而后把Vue以及其余参数传入你的install方法,而后呢,咱们就能够在这一步上动点手脚了。

话很少说,看(代)码:

let _Vue;
const install = (vm, options) => {
    _Vue = vm;
    _Vue.mixin({
        beforeCreate() {
            if (this.$parent) {
                this.$store = this.$parent.$store
            } else {
                this.$store = this.$options && this.$options.store
            }
        }
    })
}

export default {install}
复制代码

首先咱们会先把Vue保存一下,留待他用(管他待会用不用,先留着)。而后接下来就是咱们上面提出的第一个问题怎么实现的答案了,使用Vue内部的mixin方法,给每一个实例混入一个钩子函数。

什么是混入,这里伪装你们都知不道,稍微介绍一下。以上面代码为例,差很少就是给每一个实例都添加一个beforeCreate方法,它不会覆盖原有的钩子函数,而是会一块儿执行。

而后咱们能够先判断是不是子组件,若是是就拿到它父组件的$store赋值给当前子组件的store上,若是不是子组件,就能够从$options上拿到$store,这样一来,全部Vue实例就都有了$store属性了。

state实现

其实说白了,state不就是一个储存一些属性的对象而已,换了张脸我仍是认识你王麻子。

上(代)码:

class Store {
    constructor(options) {
        /**保存一份到自己实例 */
        this._options = options;
    }
    get state() {
        return this._options.state
    }
}
复制代码

劫持一下获取方式,实际上就是在访问你传进来的对象。固然,若是只是这么写仍是有点问题的,它无法根据数据改变来自动让界面更新。

数据改变如何更新界面

其实这个问题真挺好解决的,笔者在问题中都已经提醒你了,不信你回去再看看

咱们借用Vue实例具备数据界面绑定的特性,因而咱们给state稍微包装一下就能够实现咱们要的效果了。

/**借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */
    this.vm = new _Vue({
        data: {
            state: options.state
        }
    })
复制代码

而后再修改一下对应的get方法

get state() {
        return this.vm.state
    }
复制代码

getters实现

其实从用法上看,getters用法和咱们的computed真挺像的。从表现形式上看,它其实根本上就是个函数,只不过人家内部代替你执行了。

/**简化代码,封装遍历方法 */
    const forEach = (obj, callback) => {
        Object.keys(obj).forEach((key) => {
            callback(key, obj[key])
        })
    }
    /**保存getters */
    this.getters = {};
    let getters = this._options.getters || {}
    /**遍历保存传入的getters,监听状态改变从新执行该函数 */
    forEach(getters, (getterName, fn) => {
        Object.defineProperty(this.getters, getterName, {
            get: () => {
                return fn(this.state)
            }
        })
    })
复制代码

这里咱们遍历了用户传入的getters对象,而后把拿到的属性名写入到实例的getters对象上,并绑定了对应的get方法。这个地方咱们用上了咱们熟悉的属性劫持来控制怎么给用户返回咱们想给他的值。

同时,由于getter中的第一个参数是state对象,因此咱们在执行get方法的时候会把state做为参数传入执行,并返回函数执行结果给用户。

彷佛实现起来并不怎么难看懂,估计部分小伙伴会想,这货该不会在讹我吧,怎么可能这么简单。

mutations实现

其实吧,笔者以为mutations这个东东有点像咱们用过的methods对象,一样是一个对象上绑定了不少函数,只不过用法上面看上去彷佛不太同样,咱们通常都是使用commit方法来调用一个mutation函数,而后传入两个参数state、payload

看到这个payload,就会有小伙伴问了,啥是payload啊?官方文档称之为载荷,名字挺高大上的哈,其实就是接收用户传入的参数。

来看看笔者的实现吧。

/**保存mutations */\
    constructor(options) {
        this.mutations = {};
        let mutations = this._options.mutations || {};
        forEach(mutations, (mutationName, fn) => {
            this.mutations[mutationName] = (payload) => {
                return fn(this.state, payload)
            }
        })
    }
    ...
    commit = (type, payload) => {
        this.mutations[type](payload);
    }
复制代码

先把用户传入的mutations对象的属性和方法保存到Vuex实例上,而后让用户调用commit方法的时候来指向对应的函数,并把statepayload传入。

乍一看,彷佛和getters实现差不太多,只不过这里没用到属性劫持了,而是给你包装了一下调用方式。(反正笔者是这么理解的)

actions实现

对于这个家伙的用途,相信部分小伙伴也能轻松的说出,是的,官方推荐通常的应用场景就是处理一些异步事件,而mutations通常用于处理同步事件。下面贴上官方的一张概念图你就能理解了。

这时候又有小伙伴要说了,我用mutations同样的能够实现啊。是的,用mutations固然也行,不过只是不符合Vuex的设计理念而已。

若是对于一些异步事件使用mutations会出现devtools没法捕获到这个事件的记录,因此咱们最好仍是走走寻常路,跟着官方走吧。

/**保存actions */
    constructor(options) {
        this.actions = {};
        let actions = this._options.actions || {};
        forEach(actions, (actionName, fn) => {
            this.actions[actionName] = (payload) => {
                return fn(this, payload)
            }
        })
    }
    ...
    dispatch = (type, payload) => {
        this.actions[type](payload)
    }
复制代码

它实现起来仍是挺像mutations的(毕竟两个好基友嘛),他们从代码层次上看差不太多,只是在传参方面和调用方法方面有点差别,一个用commit,参数是state,payload;一个是dispatch,传参是一个Vuex实例(实际上并非的,由于涉及到modules,下文将会讲到)。

具体代码实现和mutations差很少,笔者这就很少啰嗦了,不过在这里笔者仍是要提醒一下,由于通常来讲咱们在使用actions的时候都是用的解构,获取commit,以及一些其余参数,因此咱们在这里须要注意下this指向的问题,笔者这里用的是箭头函数来解决了这个问题。

完整代码

let _Vue;
/**简化代码,封装遍历方法 */
const forEach = (obj, callback) => {
    Object.keys(obj).forEach((key) => {
        callback(key, obj[key])
    })
}
class Store {
    constructor(options) {
        /**借用Vue的双向绑定机制让Vuex中data变化实时更新界面 */
        this.vm = new _Vue({
            data: {
                state: options.state
            }
        })
        /**保存一份到自己实例 */
        this._options = options;
        /**保存getters */
        this.getters = {};
        let getters = this._options.getters || {}
        /**遍历保存传入的getters,监听状态改变从新执行该函数 */
        forEach(getters, (getterName, fn) => {
            Object.defineProperty(this.getters, getterName, {
                get: () => {
                    return fn(this.state)
                }
            })
        })

        /**保存mutations */
        this.mutations = {};
        let mutations = this._options.mutations || {};
        forEach(mutations, (mutationName, fn) => {
            this.mutations[mutationName] = (payload) => {
                return fn(this.state, payload)
            }
        })

        /**保存actions */
        this.actions = {};
        let actions = this._options.actions || {};
        forEach(actions, (actionName, fn) => {
            this.actions[actionName] = (payload) => {
                return fn(this, payload)
            }
        })
    }
    get state() {
        return this.vm.state
    }
    commit = (type, payload) => {
        this.mutations[type](payload);
    }
    dispatch = (type, payload) => {
        this.actions[type](payload)
    }
}
const install = (vm, options) => {
    _Vue = vm;
    _Vue.mixin({
        beforeCreate() {
            if (this.$parent) {
                this.$store = this.$parent.$store
            } else {
                this.$store = this.$options && this.$options.store
            }
        }
    })
}
export default {
    install,
    Store
};
复制代码

修行不易,前端的世界老是突飞猛进,要跟上它的步伐仍是要多折腾折腾。

今天笔者就暂时说到这了,小伙伴以为有帮助的话就给笔者点个赞呗。

贴上笔者我的网站地址: 烟雨的我的博客

源码github地址:my_vuex

相关文章
相关标签/搜索