vuex原理

302 阅读2分钟

Vue.use

会判断传入的为对象还是方法,如果传入的是一个对象则查看该对象的install属性,如果为方法则直接调用传入的方法。为了防止重复添加插件,使用了一个installedPlugins数组存储已注册的插件,使用indexOf来判断是否已经存在该插件

Vue.use = function(plugin){
 const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
 if(installedPlugins.indexOf(plugin)>-1){
  return this;
 }
 <!-- 其他参数 -->
 const args = toArray(arguments,1);
 args.unshift(this);
 if(typeof plugin.install === 'function'){
  plugin.install.apply(plugin,args);
 }else if(typeof plugin === 'function'){
  plugin.apply(null,plugin,args);
 }
 installedPlugins.push(plugin);
 return this;
}

vux原理

全部组件添加store实例(Vue.use()来调用 )

下面代码为使用vuex的代码,因为调用了Vue.use(),所以可以推断出vuex里面有一个install方法。

import Vuex from 'vuex';
Vue.use(Vuex)

使用mixin的方式,并且利用组件的创建顺序父beforeCreated —> 父created —> 父beforeMounted —> 子beforeCreated —> 子created —> 子beforeMounted —> 子mounted —> 父mounted,来给每一个组件进行store的引用的复制,从而使每个组件都拥有了同一个$store挂载到身上。 + 为什么是使用beforeCreated生命周期(目的为了让store也实现响应式):因为creted的时候option以及初始化好了。即vuex的实例store存储的数据也是实现了数据的双向绑定,如果在created进行混入,即data上的数据已经执行完了初始化操作,这样的话store就不具备响应式效果了。

let install = function(Vue){
    Vue.mixin({
        beforeCreate(){
            if (this.$options && this.$options.store){ // 如果是根组件
                this.$store = this.$options.store
            }else { //如果是子组件
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}

构造函数(new Vuex.Store())

因为我们使用的时候是使用为以下模式 ,可以推断出vuex的源码里面存在一个Store的构造函数。

//注册实例
new Vuex.Store({
    state:{},
    mutation:{}
    ....
})
Store构造函数传入的参数为有state,mutition等属性的一个对象,因此store的入参可以确认为一个对象options
  • state: state因为通过options传入的就是一个state的,我们可以直接在里面定义一个实例的state属性,然后就可以了,即如:
class  Store{
    constructor(options) {
        this.state = options.state
    }
}

但是这样操作的情况下,不具备vuex具有的响应式,所以我们可以使用vue的响应式来实现,通过new Vue来实现数据响应式。即:

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

又但是我们在使用state的时候使用方式为this.$store.state.xxx,但是如果现在需要调用xxx属性,则需要通过this.$store.vm.state.xxx来调用,因此我们可以使用Object.defineProperty来劫持用户使用this.$store.state.xxx获取属性的时候,触发get()从而调用this.$store.vm.state.xxx,即

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

    }
    //新增代码---效果等同于Object.defineProperty
    get state(){
        return this.vm.state
    }
}
  • getter: 首先我们知道getter的参数传入getter:{getName(state){return state.name}}以及调用方式this.$store.getter.getName。我们只需要在调用this.$store.getter.getName的时候将通过options传入的对应的key的方法进行执行就可以了。因此我们需要监听this.$store.getter.getName,即使用Object.defineProperty监听用户调用了实例getter的某个key,然后通过key来调用传入的getter的对应key的方法。这也是为什么在使用getter的时候,传入的参数为方法,但是调用的时候为什么可以不写括号来调用的原因。
class  Store{
    constructor(options) {
        this.vm = new Vue({
            data: {
                state: options.state
            }
        })
        //新增
        this.getter={}
        Object.keys(option.getter).forEach(key => {
            Object.defineProperty(this.getter,key,{
                get(){
                    return option.getter[key](this.state)
                }
            })
        })
    }
    get state(){
        return this.vm.state
    }
}

Object.defineProperty
  • mutation: 首先我们得知道mutation的传入方式mutation: { set_name(state,val) {state.name = val}}以及调用方式this.$store.commit('set_name','张三')。首先入参为带了state,因此可以判断实例是对了传入的mutation进行了遍历并且给每个方法都传入实例的state作为第一个参数,因为val是给用户传入的即对应的为commit的第二个参数,因此遍历完的mutation肯定是返回一个第一个带一个参数的方法,即。
    class  Store{
        constructor(options) {
            this.vm = new Vue({
                data: {
                    state: options.state
                }
            })

            Object.keys(option.getter).forEach(key => {
                Object.defineProperty(this.getter,key,{
                    get(){
                        return option.getter(this.state)
                    }
                })
            })
            
            //新增
            this.mutation = {}
            Object.keys(options.mutation).forEach(key => {
                this.mutation[key] = function(arg){
                    options.mutation[key](this.state,arg)
                }
            })
        }
        //新增
        commit(key,val){
            return this.mutation[key](val)
        }

        get state(){
            return this.vm.state
        }
    }

    Object.defineProperty
  • action: action原理和上面差不多,更多详细参考

参考

手写vuex