理解 vuex 实现原理

本章重点讲解一下 vuex 的实现原理,vue

由于代码中注释比较多,显得代码比较冗余,因此最好把源码下载下来,能够把注释删了看一下,其实没有多少代码ios

开胃小菜

Vuex是什么?git

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的全部组件的状态,vue-router

这个状态管理应用包含如下几个部分:vuex

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入致使的状态变化。

给出一张官方的“单向数据流”理念的简单示意:后端

图片加载失败!

每个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。api

Vuex 和单纯的全局对象有如下两点不一样:数组

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地获得高效更新。(也就是所谓的MVVM)
  • 你不能直接改变 store 中的状态。改变 store 中的状态的惟一途径就是显式地提交 (commit) mutations

看图了解工做原理:bash

图片加载失败!

若是理解了这张图,你就能知道vuex的工做原理了app

须要注意的点:

  • 改变状态的惟一途径就是提交mutations
  • 若是是异步的,就派发(dispatch)actions,其本质仍是提交mutations
  • 怎样去触发actions呢?能够用组件Vue Components使用dispatch或者后端接口去触发
  • 提交mutations后,能够动态的渲染组件Vue Components

以为是否是少了什么,没错,就是getters 下面原理实现的时候会说

原理实现

准备工做

首先把不须要的文件和代码全删了,经典化结构,以下:

图片加载失败!

App.vue代码:

<template>
   <div>
      <!-- vuex 把状态放到一个公共的地方,哪一个组件使用,就直接能够从公共的地方获取状态 -->
   </div>
</template>
<script>
export default {
   name:'app',
}  
</script>
复制代码

main.js代码:

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from 'vue-router'

Vue.config.productionTip = false

new Vue({
  name:'main',
  router, //封装了 router-view router-link $router $route
  store,  //写到这里,说明所有的组件均可以使用store
  render: h => h(App)
}).$mount('#app')
复制代码

store.js代码:

import Vue from 'vue'
//把里面的全删了,本身写

// 引入本身的写的vuex,里面有一个对象{install},当你use时,会自动调用这个方法
//导入vuex {install Store}
import Vuex from './vuex'

Vue.use(Vuex) 

//须要建立一个仓库并导出
//当new的时候,给Vuex.js中传入了一堆的东西
export default new Vuex.Store({
    state:{
        name:'Fan'
    },
    //getters中虽然是一个方法,可是用时,能够把他看成属性
    getters:{   // 说白了,就是vue中data中的computed
        
    },
    // 改变状态:异步请求数据  事件 
    mutations:{

    },
    actions:{
        
    }
})
复制代码

vuex.js文件中的代码先不写,下面开始写

实现state

上面准备工做作好,接下来就实现咱们的state

vuex.js中写以下代码(具体说明和操做已在代码中注释):

//定义一个Vue,让全局均可以使用这个Vue
let Vue;

class Store{
    //当new的时候,给Vuex.js中传入了一堆的东西,在这里接收须要用constructor
    constructor(options){
        // console.log(options);   //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能够拿到里面的数据了
        
/*-------------------------------state原理-------------------------------------------------------------*/
        //给每一个组件的$store上挂一个state,让每一个组件均可以用  this.$store.state
        this.state = options.state

        //在state上面传入一个name:'Fan'打印一下
        // console.log(this.state);    //打印结果  {name: "Fan"}
/*-------------------------------------------------------------------------------------------------*/

    }
}

//install本质上就是一个函数
const install = (_Vue)=>{
    // console.log('......');  //测试能不能调到这个方法,经测试能够调到
    //把构造器赋给全局Vue
    Vue = _Vue;

    //混入
    Vue.mixin({
        beforeCreate() {    //表示在组件建立以前自动调用,每一个组件都有这个钩子
            // console.log(this.$options.name) //this表示每一个组件,测试,能够打印出mian.js和App.vue中的name main和app
            
            //保证每个组件都能获得仓库
            //判断若是是main.js的话,就把$store挂到上面
            if(this.$options && this.$options.store){
                this.$store = this.$options.store
            }else{
                //若是不是根组件的话,也把$store挂到上面,由于是树状组件,因此用这种方式
                this.$store = this.$parent && this.$parent.$store

                //在App.vue上的mounted({console.log(this.$store)})钩子中测试,能够获得store ---> Store {}
            }
        },
    })
}

//导出
export default {
    install,
    Store
}
复制代码

这样的话,所有的组件均可以使用this.$store.state这个方法了

实现getters

首先在store.js中的getters中定义两个方法,用来测试:

//getters中虽然是一个方法,可是用时,能够把他看成属性
getters:{   // 说白了,就是vue中data中的computed
    myName(state){
        return state.name+'Jun'
    },
    myAge(){
        
    }
},
复制代码

而后在vuex.js文件中的Store类的constructor中来写咱们的代码,以下:

class Store{
    //当new的时候,给Vuex.js中传入了一堆的东西,在这里接收须要用constructor
    constructor(options){
        // console.log(options);   //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能够拿到里面的数据了
        
/*------------------------------------state原理--------------------------------------------------------*/
        //给每一个组件的$store上挂一个state,让每一个组件均可以用  this.$store.state
        // this.state = options.state
/*-------------------------------------------------------------------------------------------------*/

/* --------------------------------状态响应式原理---------------------------------------------------------------- */
        // 上面那种写法不完美,当改变数据的时候,不能动态的渲染,因此须要把data中的数据作成响应式的
        //_s在下面的 get state方法中使用
        this._s = new Vue({
            data:{
                // 只有data中的数据才是响应式
                state:options.state
            }
        })

        
        //在state上面传入一个name:'Fan'打印一下
        // console.log(this.state);    //打印结果  {name: "Fan"}
/* ------------------------------------------------------------------------------------------------ */

/*---------------------------------getters原理-----------------------------------------------------------*/
        //获得仓库中的getters,若是人家不写getters的话,就默认为空
        let getters = options.getters || {}
        // console.log(getters);   //打印出一个对象,对象中是一个方法  {myName: ƒ}

        //给仓库上面挂载一个getters,这个getters和上面的那一个getters不同,一个是获得,一个是挂载
        this.getters = {}

        //很差理解,由于人家会给你传多个方法,因此使用这个api处理获得的getters,获得一个数组
        //把store.js中的getters中再写一个方法myAge,用来测试
        // console.log(Object.keys(getters));  //打印出  ["myName", "myAge"]

        //遍历这个数组,获得每个方法名
        Object.keys(getters).forEach((getter)=>{
            // console.log(getter);    //打印出  myName   myAge
            Object.defineProperty(this.getters,getter,{
                //当你要获取getter的时候,会自动调用get这个方法
                //必定要用箭头函数,要否则this指向会出现问题
                get:()=>{
                    console.log(this);
                    return getters[getter](this.state)
                }
            })
        })
/*-------------------------------------------------------------------------------------------------*/

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

而后在App.vue中测试:

<template>
   <div>
      <!-- vuex 把状态放到一个公共的地方,哪一个组件使用,就直接能够从公共的地方获取状态 -->
      {{this.$store.state.name}}
      <!-- 打印出 Fan -->
      {{this.$store.getters.myName}}   
      <!-- 打印出 FanJun -->
      
   </div>
</template>
<script>
export default {
   name:'app',
   mounted(){
      console.log(this.$store);
   }
}  
</script>
复制代码

实现mutations

先用人家的试一下:

App.vue中定义一个add方法,上面定义一个按钮用来触发这个方法,代码:

<template>
   <div>
      <!-- vuex 把状态放到一个公共的地方,哪一个组件使用,就直接能够从公共的地方获取状态 -->
      {{this.$store.state.name}}
      <!-- 打印出 Fan -->
      {{this.$store.getters.myName}}   
      <!-- 打印出 FanJun -->
      <hr>
      {{this.$store.state.age}}
      <button @click="add()">Add</button>
      
   </div>
</template>
<script>
export default {
   name:'app',
   mounted(){
      console.log(this.$store);
   },
   methods:{
      add(){
         //commit一个mutations
         this.$store.commit('add',10)
      }
   }
}  
</script>
复制代码

store.js中用人家的vuex:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    state:{
        name:'Fan',
        age:10
    },
    //getters中虽然是一个方法,可是用时,能够把他看成属性
    getters:{   // 说白了,就是vue中data中的computed
        myName(state){
            return state.name+'Jun'
        },
        myAge(){

        }
    },
    // 改变状态:异步请求数据  事件 
    mutations:{
        add(state,payload){
            state.age += payload
        }
    },
})

复制代码

此次当点击Add按钮的时候,就能实现 加10 操做

而后本身写:

store.js中写上mutations,而且定义两个方法:

// 改变状态:异步请求数据  事件 
mutations:{
	add(state,payload){
		state.age += payload
	},
	sub(){

	}
},
复制代码

而后在vuex.js中的类Store中实现:

class Store{
    //当new的时候,给Vuex.js中传入了一堆的东西,在这里接收须要用constructor
    constructor(options){
        // console.log(options);   //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能够拿到里面的数据了
        
/*-------------------------------state原理-------------------------------------------------------------*/
        //给每一个组件的$store上挂一个state,让每一个组件均可以用  this.$store.state
        // this.state = options.state
/*----------------------------------------------------------------------------------------------------*/

/* --------------------------------状态响应式原理---------------------------------------------------------------- */
        // 上面那种写法不完美,当改变数据的时候,不能动态的渲染,因此须要把data中的数据作成响应式的
        //_s在下面的 get state() 方法中使用
        this._s = new Vue({
            data:{
                // 只有data中的数据才是响应式
                state:options.state
            }
        })
        
        //在state上面传入一个name:'Fan'打印一下
        // console.log(this.state);    //打印结果  {name: "Fan"}
/* ----------------------------------------------------------------------------------------------------------------- */

/* ----------------------------------getters原理------------------------------------------------------------- */    

        //获得仓库中的getters,若是人家不写getters的话,就默认为空
        let getters = options.getters || {}
        // console.log(getters);   //打印出一个对象,对象中是一个方法  {myName: ƒ}

        //给仓库上面挂载一个getters,这个getters和上面的那一个getters不同,一个是获得,一个是挂载
        this.getters = {}

        //很差理解,由于人家会给你传多个方法,因此使用这个api处理获得的getters,获得一个数组
        //把store.js中的getters中再写一个方法myAge,用来测试
        // console.log(Object.keys(getters));  //打印出  ["myName", "myAge"]

        //遍历这个数组,获得每个方法名
        Object.keys(getters).forEach((getter)=>{
            // console.log(getter);    //打印出  myName   myAge
            Object.defineProperty(this.getters,getter,{
                //当你要获取getter的时候,会自动调用get这个方法
                //必定要用箭头函数,要否则this指向会出现问题
                get:()=>{
                    // console.log(this);
                    return getters[getter](this.state)
                }
            })
        })
/* -------------------------------------------------------------------------------------------------- */
    
/* ---------------------------------------mutatios原理----------------------------------------------------------- */
        //和getters思路差很少

        //获得mutations
        let mutations = options.mutations || {}
        // console.log(mutations);     //{add: ƒ}

        //挂载mutations
        this.mutations = {}

        //拿到对象中的一堆方法
        Object.keys(mutations).forEach((mutation)=>{
            // console.log(mutation);  //add sub
            this.mutations[mutation] = (payload)=>{
                mutations[mutation](this.state,payload)
            }
        })

        //打印看一下,正确
        // console.log(mutations);     //{add: ƒ, sub: ƒ}
        
        //可是他比较恶心,须要实现commit,在下面实现
/* -------------------------------------------------------------------------------------------------- */

    } 

    //给store上挂一个commit,接收两个参数,一个是类型,一个是数据
    commit(type,payload){
        //{add: ƒ, sub: ƒ}
        //把方法名和参数传给mutations
        this.mutations[type](payload)
    }

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

App.vue中测试:

<template>
   <div>
      <!-- vuex 把状态放到一个公共的地方,哪一个组件使用,就直接能够从公共的地方获取状态 -->
      {{this.$store.state.name}}
      <!-- 打印出 Fan -->
      {{this.$store.getters.myName}}   
      <!-- 打印出 FanJun -->
      <hr>
      {{this.$store.state.age}}
      <button @click="add()">Add</button>
      
   </div>
</template>
<script>
export default {
   name:'app',
   mounted(){
      // console.log(this.$store);
   },
   methods:{
      add(){
         //commit一个mutations
         this.$store.commit('add',10)
      }
   }
}  
</script>
复制代码

由于代码比较冗余,因此我简化了代码,就是把公共的方法Object.keys(obj).forEach(key => { callback(key, obj[key]) })抽离出来。

能够下载源码看一下,这里就很少说了

实现actions

一样的,在vuex.js中的类Store中实现,由于我简化了代码,因此总体复制下来看一下,

这里把dispatchcommit方法换成了箭头函数,防止this指向出现问题

//定义一个Vue,让全局均可以使用这个Vue
let Vue;

// forEach是用来循环一个对象
const forEach = (obj, callback) => {
    // 把数组中的每个key获得  objc[key] 
    // key  value  ----> callback
    Object.keys(obj).forEach(key => {
        callback(key, obj[key])
    })
}

class Store {
    //当new的时候,给Vuex.js中传入了一堆的东西,在这里接收须要用constructor
    constructor(options) {
        // console.log(options);   //打印出{state: {…}, getters: {…}, mutations: {…}, actions: {…}},就能够拿到里面的数据了
        
/*-------------------------------state原理-------------------------------------------------------------*/
        //给每一个组件的$store上挂一个state,让每一个组件均可以用  this.$store.state
        // this.state = options.state
/*----------------------------------------------------------------------------------------------------*/

/* ---------------------------------------状态响应式原理--------------------------------------------------------- */
        // 上面那种写法不完美,当改变数据的时候,不能动态的渲染,因此须要把data中的数据作成响应式的
        //_s在下面的 get state方法中使用
        this._s = new Vue({
            data: {
                // 只有data中的数据才是响应式
                state: options.state
            }
        })
/* ----------------------------------------------------------------------------------------------------------------- */

/* ----------------------------------------getters原理------------------------------------------------------- */
        //在state上面传入一个name:'Fan'打印一下
        // console.log(this.state);    //打印结果  {name: "Fan"}

        //获得仓库中的getters,若是人家不写getters的话,就默认为空
        let getters = options.getters || {}
        // console.log(getters);   //打印出一个对象,对象中是一个方法  {myName: ƒ}

        //给仓库上面挂载一个getters,这个getters和上面的那一个getters不同,一个是获得,一个是挂载
        this.getters = {}

        //很差理解,由于人家会给你传多个方法,因此使用这个api处理获得的getters,获得一个数组
        //把store.js中的getters中再写一个方法myAge,用来测试
        // console.log(Object.keys(getters));  //打印出  ["myName", "myAge"]

        forEach(getters, (getterName, value) => {
            Object.defineProperty(this.getters, getterName, {
                get: () => {
                    return value(this.state)
                }
            })
        })
/* -------------------------------------------------------------------------------------------------- */

/* ----------------------------------------mutatios原理---------------------------------------------------------- */
        //和getters思路差很少

        //获得mutations
        let mutations = options.mutations || {}
        // console.log(mutations);     //{add: ƒ}

        //挂载mutations
        this.mutations = {}

        forEach(mutations, (mutationName, value) => {
            this.mutations[mutationName] = (payload) => {
                value(this.state, payload)
            }
        })

        //打印看一下,正确
        // console.log(mutations);     //{add: ƒ, sub: ƒ}
        //可是他须要实现commit,在下面实现
/* -------------------------------------------------------------------------------------------------- */

/* ---------------------------------------------actions原理----------------------------------------------------- */
        //和上面两种大同小异,很少注释了
        let actions = options.actions || {}
        this.actions = {};
        forEach(actions, (action, value) => {
            this.actions[action] = (payload) => {
                value(this, payload)
            }
        })
/* -------------------------------------------------------------------------------------------------- */

    }
    // type是actions的类型  
    dispatch = (type, payload) => {
        this.actions[type](payload)
    }

    //给store上挂一个commit,接收两个参数,一个是类型,一个是数据
    commit = (type, payload) => {
        //{add: ƒ, sub: ƒ}
        this.mutations[type](payload)
    }

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

//install本质上就是一个函数
const install = (_Vue) => {
    // console.log('......');  //测试能不能调到这个方法,经测试能够调到
    //把构造器赋给全局Vue
    Vue = _Vue;

    //混入
    Vue.mixin({
        beforeCreate() { //表示在组件建立以前自动调用,每一个组件都有这个钩子
            // console.log(this.$options.name) //this表示每一个组件,测试,能够打印出mian.js和App.vue中的name main和app

            //保证每个组件都能获得仓库
            //判断若是是main.js的话,就把$store挂到上面
            if (this.$options && this.$options.store) {
                this.$store = this.$options.store
            } else {
                //若是不是根组件的话,也把$store挂到上面,由于是树状组件,因此用这种方式
                this.$store = this.$parent && this.$parent.$store

                //在App.vue上的mounted()钩子中测试,能够获得store ---> Store {}
            }
        },
    })
}

//导出
export default {
    install,
    Store
}
复制代码

mutations中添加一个异步方法:

mutations: {
	add(state, payload) {
		state.age += payload
	},
	sub() {

	},
	asyncSub(state, payload) {
		state.age -= payload
	}
},
复制代码

store.js中写一个actions

actions: {
	asyncSub({commit}, payload) {
		setTimeout(() => {
			commit("asyncSub", payload)
		}, 2000)
	}
} 
复制代码

最后在App.vue中定义方法测试:

<template>
   <div>
      <!-- vuex 把状态放到一个公共的地方,哪一个组件使用,就直接能够从公共的地方获取状态 -->
      {{this.$store.state.name}}
      <!-- 打印出 Fan -->
      {{this.$store.getters.myName}}
      <!-- 打印出 FanJun -->
      <hr> {{this.$store.state.age}}
      <!-- 同步加 -->
      <button @click="add">Add</button>
      <!-- 异步减 -->
      <button @click="sub">Async Sub</button>
   </div>
</template>
<script>
export default {
  name: "app",
  mounted() {
    // console.log(this.$store);
    // 是异步的
    setTimeout(() => {
      this.$store.state.age = 666;
    }, 1000);
    // 是同步的
    console.log(this.$store.state);
  },
  methods: {
    add() {
      //commit一个mutations
      this.$store.commit("add", 10);
    },
    sub(){
      this.$store.dispatch("asyncSub",10)
    }
  }
};
</script>
复制代码

删去注释的vuex.js代码

其实并无多少代码

let Vue;
const forEach = (obj, callback) => {
    Object.keys(obj).forEach(key => {
        callback(key, obj[key])
    })
}
class Store {
    constructor(options) {
        this._s = new Vue({
            data: {
                state: options.state
            }
        })
        let getters = options.getters || {}
        this.getters = {};
        forEach(getters, (getterName, value) => {
            Object.defineProperty(this.getters, getterName, {
                get: () => {
                    return value(this.state)
                }
            })
        })
        let mutations = options.mutations || {}
        this.mutations = {};
        forEach(mutations, (mutationName, value) => {
            this.mutations[mutationName] = (payload) => {
                value(this.state, payload)
            }
        })
        
        let actions = options.actions || {}
        this.actions = {};
        forEach(actions,(actionName,value)=>{
            this.actions[actionName] = (payload)=>{
                value(this,payload)
            }
        })
    }
    dispatch=(type,payload)=>{
        this.actions[type](payload)
    }
    commit=(type, payload)=>{
        this.mutations[type](payload)
    }
    get state() {
        return this._s.state
    }
}
const install = _Vue => {
    Vue = _Vue
    Vue.mixin({
        beforeCreate() {
            if (this.$options && this.$options.store) {
                this.$store = this.$options.store
            } else {
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}
export default { install, Store }
复制代码

总述

由于注释太多,显得很复杂,因此最好把源码下载下来,本身去尝试写一下

附上源码地址:Vuex实现原理


^_<

相关文章
相关标签/搜索