如何手写一个简易的 Vuex


Posted in Javascript onOctober 10, 2020

前言

本文适合使用过 Vuex 的人阅读,来了解下怎么自己实现一个 Vuex。

基本骨架

这是本项目的src/store/index.js文件,看看一般 vuex 的使用

import Vue from 'vue'
import Vuex from './myvuex' // 引入自己写的 vuex
import * as getters from './getters'
import * as actions from './actions'
import state from './state'
import mutations from './mutations'

Vue.use(Vuex) // Vue.use(plugin)方法使用vuex插件

// vuex 导出一个类叫Store,并传入对象作为参数
export default new Vuex.Store({
 state,
 mutations,
 actions,
 getters,
})

Vue.use的用法:

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。

  • 该方法需要在调用 new Vue() 之前被调用。
  • 当 install 方法被同一个插件多次调用,插件将只会被安装一次。

即是我们需要在./myvuex.js中导出 install方法,同时导出一个类Store,于是第一步可以写出代码:

let Vue = null

class Store {
 constructor(options) {}
}

function install(_Vue) {
 Vue = _Vue // 上面Store类需要能获取到Vue
}

export default {
 Store,
 install,
}

install 方法

当我们使用 vuex 的时候,每一个组件上面都有一个this.$store属性,里面包含了 state,mutations, actions, getters 等,所以我们也需要在每个组件上都挂载一个$store 属性,要让每一个组件都能获取到,这里我们使用Vue.mixin(mixin),用法介绍如下:

全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。可以使用混入向组件注入自定义的行为,它将影响每一个之后创建的 Vue 实例。

function install(_Vue) {
 Vue = _Vue // install方法调用时,会将Vue作为参数传入(上面Store类需要用到Vue)
 // 实现每一个组件,都能通过this调用$store
 Vue.mixin({
  beforeCreate() {
   // 通过this.$options可以获取new Vue({参数}) 传递的参数
   if (this.$options && this.$options.store) {
    // 证明这个this是根实例,也就是new Vue产生的那个实例
    this.$store = this.$options.store
   } else if (this.$parent && this.$parent.$store) {
    // 子组件获取父组件的$store属性
    this.$store = this.$parent.$store
   }
  },
 })
}

state

由于 Vuex 是基于 Vue 的响应式原理基础,所以我们要让数据改变可刷新视图,则需要创建一个 vue 实例

class Store {
 // options 即是 Vuex.Store({})传入的参数
 constructor(options) {
  // vuex 的核心就是借用了vue的实例,因为vue的实例数据变化,会刷新视图
  let vm = new Vue({
   data: {
    state: options.state,
   },
  })
  // state
  this.state = vm.state
 }
}

commit

我们使用 vuex 改变数据时,是触发 commit 方法,即是这样使用的:

this.$store.commit('eventName', '参数' );

所以我们要实现一个commit方法,把 Store 构造函数传入的 mutations 做下处理

class Store {
 constructor(options) {
  // 实现 state ...

  // mutations
  this.mutations = {} // 存储传进来的mutations
  let mutations = options.mutations || {}
  // 循环取出事件名进行处理(mutations[事件名]: 执行方法)
  Object.keys(mutations).forEach(key => {
   this.mutations[key] = params => {
    mutations[key].call(this, this.state, params) // 修正this指向
   }
  })
 }

 commit = (key, params) => {
  // key为要触发的事件名
  this.mutations[key](params)
 }
}

dispatch

跟上面的 commit 流程同理

class Store {
 constructor(options = {}) {
  // ...

  // actions
  this.actions = {}
  let actions = options.actions || {}
  Object.keys(actions).forEach(key => {
   this.actions[key] = params => {
    actions[key].call(this, this, params)
   }
  })
 }

 dispatch = (type, payload) => {
  this.actions[type](payload)
 }
}

getters

getters 实际就是返回 state 的值,在使用的时候是放在 computed 属性,每一个 getter 都是函数形式;

getters 是需要双向绑定的。但不需要双向绑定所有的 getters,只需要绑定项目中事件使用的 getters。

这里使用Object.defineProperty()方法,它会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

class Store {
 constructor(options = {}) {
  // ...

  // getters
  this.getters = {}
  let getters = options.getters || {}
  Object.keys(getters).forEach(key => {
   Object.defineProperty(this.getters, key, {
    get: () => {
     return getters[key].call(this, this.state)
    },
   })
  })
 }
}

到此为止,已经可以使用我们自己写的 vuex 做一些基本操作了,但只能通过this.$store.xx的形式调用,故需要再实现方法。

map 辅助函数

先来说说 mapState

没有 map 辅助函数之前这样使用:

computed: {
 count () {
  return this.$store.state.count
 }
}

当映射的计算属性的名称与 state 的子节点名称相同时,给 mapState 传一个字符串数组。

computed: {
 // 使用对象展开运算符将此对象混入到外部对象中
 ...mapState(['count'])
}

我们这里简单就只实现数组的情况

export const mapState = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function() {
   return this.$store.state[item]
  }
 })
 return obj
}

之后几个 map 辅助函数都是类似

  • mapGetters
export const mapGetters = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function() {
   return this.$store.getters[item]
  }
 })
 return obj
}
  • mapMutations
export const mapMutations = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function(params) {
   return this.$store.commit(item, params)
  }
 })
 return obj
}
  • mapActions
export const mapActions = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function(payload) {
   return this.$store.dispatch(item, payload)
  }
 })
 return obj
}

完整代码

let Vue = null

class Store {
 constructor(options) {
  // vuex 的核心就是借用了vue的实例,因为vue的实例数据变化,会刷新视图
  let vm = new Vue({
   data: {
    state: options.state,
   },
  })
  // state
  this.state = vm.state

  // mutations
  this.mutations = {} // 存储传进来的mutations
  let mutations = options.mutations || {}
  Object.keys(mutations).forEach(key => {
   this.mutations[key] = params => {
    mutations[key].call(this, this.state, params)
   }
  })

  // actions
  this.actions = {}
  let actions = options.actions || {}
  Object.keys(actions).forEach(key => {
   this.actions[key] = params => {
    actions[key].call(this, this, params)
   }
  })

  // getters
  this.getters = {}
  let getters = options.getters || {}
  Object.keys(getters).forEach(key => {
   Object.defineProperty(this.getters, key, {
    get: () => {
     return getters[key].call(this, this.state)
    },
   })
  })
 }

 commit = (key, params) => {
  this.mutations[key](params)
 }

 dispatch = (type, payload) => {
  this.actions[type](payload)
 }
}

export const mapState = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function() {
   return this.$store.state[item]
  }
 })
 return obj
}

export const mapGetters = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function() {
   return this.$store.getters[item]
  }
 })
 return obj
}

export const mapMutations = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function(params) {
   return this.$store.commit(item, params)
  }
 })
 return obj
}

export const mapActions = args => {
 let obj = {}
 args.forEach(item => {
  obj[item] = function(payload) {
   return this.$store.dispatch(item, payload)
  }
 })
 return obj
}

function install(_Vue) {
 Vue = _Vue // install方法调用时,会将Vue作为参数传入(上面Store类需要用到Vue)
 // 实现每一个组件,都能通过this调用$store
 Vue.mixin({
  beforeCreate() {
   // 通过this.$options可以获取new Vue({参数}) 传递的参数
   if (this.$options && this.$options.store) {
    // 证明这个this是根实例,也就是new Vue产生的那个实例
    this.$store = this.$options.store
   } else if (this.$parent && this.$parent.$store) {
    // 子组件获取父组件的$store属性
    this.$store = this.$parent.$store
   }
  },
 })
}

export default {
 Store,
 install,
}

以上就是如何手写一个简易的 Vuex的详细内容,更多关于手写 vuex的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
js中判断用户输入的值是否为空的简单实例
Dec 23 Javascript
Jquery实现由下向上展开效果的例子
Dec 08 Javascript
基于jQuery+JSON的省市二三级联动效果
Jun 05 Javascript
javascript简单实现滑动菜单效果的方法
Jul 27 Javascript
jQuery插件版本冲突的处理方法分析
Jan 16 Javascript
Vuejs 组件——props数据传递的实例代码
Mar 07 Javascript
JavaScript切换搜索引擎的导航网页搜索框实例代码
Jun 11 Javascript
解决webpack -p压缩打包react报语法错误的方法
Jul 03 Javascript
JavaScript EventEmitter 背后的秘密 完整版
Mar 29 Javascript
vue路由守卫,限制前端页面访问权限的例子
Nov 11 Javascript
微信小程序利用button控制条件标签的变量问题
Mar 15 Javascript
JavaScript实现移动小精灵的案例代码
Dec 12 Javascript
echarts实现晶体球面投影的实例教程
Oct 10 #Javascript
详解Vue中Axios封装API接口的思路及方法
Oct 10 #Javascript
在Vue中使用Echarts实例图的方法实例
Oct 10 #Javascript
基于Vue.js+Nuxt开发自定义弹出层组件
Oct 09 #Javascript
IDEA配置jQuery, $符号不再显示黄色波浪线的问题
Oct 09 #jQuery
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
Oct 09 #Javascript
vue实现选中效果
Oct 07 #Javascript
You might like
推荐几部必看的DC动画电影
2020/03/03 欧美动漫
php 多线程上下文中安全写文件实现代码
2009/12/28 PHP
PHP zlib扩展实现页面GZIP压缩输出
2010/06/17 PHP
php跨域cookie共享使用方法
2014/02/20 PHP
PHP使用array_fill定义多维数组的方法
2015/03/18 PHP
php生成带logo二维码方法小结
2016/04/08 PHP
完美解决phpdoc导出文档中@package的warning及Error的错误
2016/05/17 PHP
PHP常量DIRECTORY_SEPARATOR原理及用法解析
2020/11/10 PHP
jquery下操作HTML控件的实现代码
2010/01/12 Javascript
JavaScript中getUTCMinutes()方法的使用详解
2015/06/10 Javascript
js+HTML5实现视频截图的方法
2015/06/16 Javascript
基于HTML+CSS+JS实现增加删除修改tab导航特效代码
2016/08/05 Javascript
NodeJS使用formidable实现文件上传
2016/10/27 NodeJs
Vue + Webpack + Vue-loader学习教程之功能介绍篇
2017/03/14 Javascript
JavaScript数据结构中串的表示与应用实例
2017/04/12 Javascript
js轮播图透明度切换(带上下页和底部圆点切换)
2017/04/27 Javascript
JavaScrpt的面向对象全面解析
2017/05/09 Javascript
AngularJS基于factory创建自定义服务的方法详解
2017/05/25 Javascript
Vue.js 2.5新特性介绍(推荐)
2017/10/24 Javascript
JS原型继承四步曲及原型继承图一览
2017/11/28 Javascript
基于IView中on-change属性的使用详解
2018/03/15 Javascript
mpvue开发音频类小程序踩坑和建议详解
2019/03/12 Javascript
Python装饰器decorator用法实例
2014/11/10 Python
Python的IDEL增加清屏功能实例
2017/06/19 Python
numpy.random.seed()的使用实例解析
2018/02/03 Python
Python自定义线程池实现方法分析
2018/02/07 Python
python3+PyQt5+Qt Designer实现扩展对话框
2018/04/20 Python
Python + Flask 实现简单的验证码系统
2019/10/01 Python
Python图片处理模块PIL操作方法(pillow)
2020/04/07 Python
KIKO比利时官网:意大利彩妆品牌
2017/07/23 全球购物
解除劳动合同协议书范本2014
2014/09/25 职场文书
部门群众路线教育实践活动对照检查材料思想汇报
2014/10/07 职场文书
打架赔偿协议书范本
2014/10/26 职场文书
2014年学校食堂工作总结
2014/11/25 职场文书
优秀班集体申报材料
2014/12/25 职场文书
小学生通知书评语
2014/12/31 职场文书