经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
从0实现一个vuex
来源:cnblogs  作者:funkyou  时间:2021/3/29 9:09:23  对本文有异议

大家知道,在开发大型vue项目时,使用vuex时不可避免的,vuex能够帮助我们在错综复杂的数据流中快速拿到自己想要的数据,提高开发效率,尽管vuex无法持久化数据,但也可以通过插件来解决该问题,总之vuex是大型项目中百利无一害的插件。

 

 

在上文我们实现了一个vue-router后,我们现在就来实现一个vuex,首先我们从vuex的原理图入手:

 

 

 

 从原理图我们可以看出,$store实例通过dispatch调用actions里的异步方法,通过commit调用mutations里的同步方法,并只能通过mutations改变state(这里插一句:非严格模式下是可以通过commit以外的方式改变state里的状态的,但在严格模式下,Vuex中修改state的唯一渠道就是执行 commit('xx', payload) 方法,其底层通过执行 this._withCommit(fn) 设置_committing标志变量为true,然后才能修改state,修改完毕还需要还原_committing变量。外部修改虽然能够直接修改state,但是并没有修改_committing标志位,所以只要watch一下state,state change时判断是否_committing值为true,即可判断修改的合法性,在严格模式下,任何 mutation 处理函数以外修改 Vuex state 都会抛出错误。)然后getters能够及时获取state中的状态并作出计算(实际上getters就是一个计算属性)

  接下来我们来简单做一个vuex的小demo,看看vuex到底实现了哪些功能:

  我们在store文件的index.js中这样写:

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. export default new Vuex.Store({
  5.   state: {
  6.     counter:0
  7.   },
  8.   mutations: {
  9. //同步自加的方法
  10.     add(state){
  11.       state.counter++
  12.     }
  13.   },
  14.   actions: {
  15. //异步自加的方法
  16.     add({commit}){
  17.       setTimeout(()=>{
  18.         commit('add')
  19.       },2000)
  20.     }
  21.   },
  22.   getters:{
  23. //获取乘二的值
  24.     doubleCounter(state){
  25.       return state.counter*2
  26. },
  27. //获取平方值
  28.     squareCounter(state){
  29.       return state.counter*state.counter
  30.     }
  31.   },
  32.   modules: {
  33.   }
  34. })

Home组件中这样写:

  1. <template>
  2.   <div class="hello">
  3.     <button @click="$store.commit('add')">counter:{{$store.state.counter}}</button>
  4.     <button @click="$store.dispatch('add')">async counter:{{$store.state.counter}}</button>
  5.     <p>double counter:{{$store.getters.doubleCounter}}</p>
  6.     <p>squareCounter:{{$store.getters.squareCounter}}</p>
  7.    <h2>这是一个Home组件</h2>
  8.    <p>我叫rick,欢迎来看我的文章:从0实现一个vuex</p>
  9.   </div>
  10. </template>

那么我们的页面大致长这样:

 

 

 

点击counter可以自加,点击async counter可以延迟两秒自加,double counter读出双倍的数值,squareCounter读出平方数值

 

接下来我们把引入的vuex换成自己自制的vuex,来继续实现现有的这些功能:

 

  1. import Vuex from './kvuex'

 

那么熟悉了原理图,设计了一个简易的vuex功能示例,接下就要来要实现我们自己的vuex啦,我们大致按照如下思路进行:

1. 首先在$store上挂载dispatch,state,getters,commmit等方法是肯定的

2. 其次要实现state的响应式,即state改变,依赖于state的所有属性都要能实现自动渲染

3. 接着实现commit和dispatch的内部方法,即为什么每次commit或dispatch都能自动调用相关的方法

4. 最后实现getters,要注意为什么这个getters能够实现只读的以及它的内部如何实现计算属性

做一个鱼骨图方便大家理解:接下来我们来逐步实现每个细节

 

 

 

1. 挂载$store

1.1 利用install和mixin

大家还记得我在实现vue-router那篇文章中的方法吗?此处我建议大家先看比较简单的vue-router的实现原理再看这篇文章哦~(见我的文章:https://www.cnblogs.com/funkyou/p/14580129.html)

我们故技重施,依然利用开发插件的方式,老规矩,复习一下vue文档:

 

 

 

为了能让所有vue组件都能通过this.$store.xxx访问到state,mutations等,那么我们就要通过全局混入的方式为vue原型上挂载一个$store的属性,全局混入用法见文档:

 

 

 

实现代码如下:

  1. // use调用时会传入Vue
  2. function install(_Vue){
  3.   // 保存Vue构造函数,插件中使用
  4.   Vue=_Vue
  5.   Vue.mixin({
  6.     beforeCreate() {
  7.       // 判断当前options选项是否含有store
  8.       if(this.$options.store){
  9.         // 如果含有就在原型上挂载这个store实例
  10.         Vue.prototype.$store=this.$options.store
  11.       }
  12.     },
  13.   })
  14. }
  15. // 因为index.js是通过Vuex接收的,所以我们要这样暴露出去(实际上Vuex={Store,install,xxx})
  16. export default{Store,install}

2. 实现state响应式

2.1:借鸡生蛋

首先我们构造一个Store实例:

 

  1. class Store{
  2.    constructor(options){   
  3.   
  4.    }
  5. }

 

 

其实这个options就是store中的配置项:

 

 

 

我们要想实现store中的数据响应式,能否借助现成的能实现响应式的示例来“借鸡生蛋”呢?(Vue实例此时瑟瑟发抖,并大喊你不要过来呀~)没错,Vue,就是你了,我们new 一个Vue实例,把data存放进去,这样我们的数据就能自动被Vue的defineProperty追踪并设置set和get属性,即可实现响应式:

  1.      // data响应式处理
  2.    this._vm= new Vue({
  3. data:{
  4. //把state藏进$$state中
  5.        $$state:options.state
  6. }
  7.   })

 

 

2.2: 利用get和set实现只读

我们希望我们的state是不能通过mutations以外的任何方式修改的,即实现只读,那么可以利用get和set属性对state进行设置:

  1.   // 存取器,store.state
  2.   get state(){
  3.     console.log(this._vm);
  4.     return this._vm._data.$$state
  5.   }
  6.   set state(v){
  7.     console.error('你无法设置state的值')
  8.   }

3.实现commit

3.1判断方法

我们要从mutations中判断出当前要用的是哪个方法,并在commit内部执行这个方法,传入state的参数,但注意要在错综复杂的this环境中先把宝贵的options.mutations保存起来:

 

  1. //  保存mutations
  2.     this._mutations=options.mutations
  3.     // 保存actions
  4.     this._actions=options.actions
  5.     // 保存getters
  6.     this._getters=options.getters

 

 

3.2偷天换日

这里我们想用的是commit(type,payload)中的type对应的方法,那么我们能否先把这个mutations[type]方法拷贝给一个函数,再在commit方法内部执行这个函数呢?答案是可行的,这就是一种偷天换日的函数复用思想:

  1.  commit(type,payload){
  2.     // 借用mutations[type]方法
  3.    const entry= this._mutations[type]
  4.    if(!entry){
  5.      console.error('unknown mutation type');
  6.    }
  7.   //  执行这个mutations[type]方法并传入state参数
  8.    entry(this.state,payload)
  9.   }

 

4. 实现dispatch

4.1注意参数

此处实现dispatch和mutations大致相同,但要注意actions和mutations中传入参数的不同:

mutations中:

 

 

actions中:

 

 

显然这里entry要传入的是store实例,在constructer中用this代指:

  1.  dispatch(type,payload){
  2.     const entry=this._actions[type]
  3.     if(!entry){
  4.       console.error('unknow action type')
  5.     }
  6.     entry(this,payload)
  7.   }

 

4.2用bind留住this

但注意,此时commit内部的this还是不是我想要设置的那个store实例了?看demo:

 

 

此时的this已经完全乱套了,所以我们还需要在commit中留住this,让他执行的永远是store实例,直接写:

  1.  //这样commit和dispatch内部的this就是当前上下文中的this,即store实例 
  2.    this.commit= this.commit.bind(this)
  3.    this.dispatch= this.dispatch.bind(this)

5. 实现getters

5.1计算属性

要想实现一个只读的getters,此处我们依然选择在Vue实例中设置这个computed方法:

  1.   // 定义computed选项和getters
  2.     const computed={}
  3.     this.getters={}
  4.    
  5. this._vm= new Vue({
  6.     data:{
  7.       $$state:options.state
  8.     },
  9.     computed
  10.   })

 

 

5.2只读属性

此处我们先保存store,随后为这个getters设置只读属性,我们可以用Object.defineProperty方法让我们能通过get读到这个方法

5.3移花接木,变无参为有参

接下来,我们想借用getters里的方法并传入state参数,但是注意:我们的getters方法是有参数的:

那么我们可以通过Object.key拿到每个方法的索引,再用一个fn保存当前索引下的方法,再在fn里传入state参数,如下:

  1.    // 保存store
  2.     const store=this
  3.     // 遍历拿到索引key,并通过store._getters[key]找到这个方法
  4.     Object.keys(this._getters).forEach(key=>{
  5.       // 获取用户定义的getter
  6.       const fn =store._getters[key]
  7.       // 转换为computed可以使用无参数形式
  8.       computed[key]=function(){
  9.         return fn(store.state)
  10.       }
  11.       // 为getters定义只读属性
  12.       Object.defineProperty(store.getters,key,{
  13.         get:()=> store._vm[key]
  14.       })
  15.     })

 

此时我们打印这个fn,它即是:

或者

 

 

getters中的方法,我们调用了它并完美的把state传了进去,这个方法是不是让人拍案叫绝~

 

接下来是全部源码:

  1. //1.插件:挂载$store
  2. // 2.实现Store
  3. let Vue  //保存Vue构造函数,插件中使用
  4. class Store{
  5.   constructor(options){
  6.    console.log(options);
  7.     //  保存mutations
  8.     this._mutations=options.mutations
  9.     // 保存actions
  10.     this._actions=options.actions
  11.     // 保存getters
  12.     this._getters=options.getters
  13.     // 定义computed选项和getters
  14.     const computed={}
  15.     this.getters={}
  16.     
  17.     // 保存store
  18.     const store=this
  19.     // 遍历拿到索引key,并通过store._getters[key]找到这个方法
  20.     Object.keys(this._getters).forEach(key=>{
  21.       // 获取用户定义的getter
  22.       const fn =store._getters[key]
  23.       // 转换为computed可以使用无参数形式
  24.       computed[key]=function(){
  25.         console.log(fn);
  26.         return fn(store.state)
  27.       }
  28.       // 为getters定义只读属性
  29.       Object.defineProperty(store.getters,key,{
  30.         get:()=> store._vm[key]
  31.       })
  32.     })
  33.      // data响应式处理
  34.    this._vm= new Vue({
  35.     data:{
  36.       $$state:options.state
  37.     },
  38.     computed
  39.   })
  40.     //这样commit和dispatch内部的this就是当前上下文中的this,即store实例 
  41.    this.commit= this.commit.bind(this)
  42.    this.dispatch= this.dispatch.bind(this)
  43.   }
  44.   // 存取器,store.state
  45.   get state(){
  46.     console.log(this._vm);
  47.     return this._vm._data.$$state
  48.   }
  49.   set state(v){
  50.     console.error('can not set')
  51.   }
  52.   commit(type,payload){
  53.     // 借用mutations[type]方法
  54.    const entry= this._mutations[type]
  55.    if(!entry){
  56.      console.error('unknown mutation type');
  57.    }
  58.   //  执行这个mutations[type]方法并传入state参数
  59.    entry(this.state,payload)
  60.   }
  61.   dispatch(type,payload){
  62.     const entry=this._actions[type]
  63.     if(!entry){
  64.       console.error('unknow action type')
  65.     }
  66.     entry(this,payload)
  67.   }
  68. }
  69. // use调用时会传入Vue
  70. function install(_Vue){
  71.   // 保存Vue构造函数,插件中使用
  72.   Vue=_Vue
  73.   Vue.mixin({
  74.     beforeCreate() {
  75.       // 判断当前options选项是否含有store
  76.       if(this.$options.store){
  77.         // 如果含有就在原型上挂载这个store实例
  78.         Vue.prototype.$store=this.$options.store
  79.       }
  80.     },
  81.   })
  82. }
  83. // 因为index.js是通过Vuex接收的,所以我们要这样暴露出去(实际上Vuex={Store,install,xxx})
  84. export default{Store,install}

 

最后看下效果:

完美实现~!如果大家想和我一起学习前端交流心得指点江山,欢迎加我的vxshq173392531

 

原文链接:http://www.cnblogs.com/funkyou/p/14587291.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号