如何手写一个简易的 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如何判断用户是否是用微信浏览器
Jun 05 Javascript
jQuery控制元素显示、隐藏、切换、滑动的方法总结
Apr 16 Javascript
js实现图片点击左右轮播
Jul 08 Javascript
jQuery超简单选项卡完整实例
Sep 26 Javascript
JavaScript 性能优化小结
Oct 12 Javascript
js实现多图左右切换功能
Aug 04 Javascript
Vue.js原理分析之observer模块详解
Feb 17 Javascript
Angular 2 利用Router事件和Title实现动态页面标题的方法
Aug 23 Javascript
JavaScript中callee和caller的区别与用法实例分析
Jun 28 Javascript
vue 自定义组件的写法与用法详解
Mar 04 Javascript
Vue中通过属性绑定为元素绑定style行内样式的实例代码
Apr 30 Javascript
JavaScript 如何计算文本的行数的实现
Sep 14 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
php 异常处理实现代码
2009/03/10 PHP
php实现首页链接查询 友情链接检查的代码
2010/01/05 PHP
php的mssql数据库连接类实例
2014/11/28 PHP
将PHP程序中返回的JSON格式数据用gzip压缩输出的方法
2016/03/03 PHP
PHP函数shuffle()取数组若干个随机元素的方法分析
2016/04/02 PHP
php生成酷炫的四个字符验证码
2016/04/22 PHP
利用PHP判断文件是否为图片的方法总结
2017/01/06 PHP
ext jquery 简单比较
2010/04/07 Javascript
jQuery让控件左右移动的三种实现方法
2013/09/08 Javascript
jquery遍历checkbox的注意事项说明
2014/02/21 Javascript
JS+CSS实现表格高亮的方法
2015/08/05 Javascript
IE8利用自带的setCapture和releaseCapture解决iframe的拖拽事件方法
2016/10/25 Javascript
Vue.js教程之axios与网络传输的学习实践
2017/04/29 Javascript
Node.js安装配置图文教程
2017/05/10 Javascript
JS实现自定义状态栏动画文字效果示例
2017/10/12 Javascript
vue 点击按钮增加一行的方法
2018/09/07 Javascript
bootstrap自定义样式之bootstrap实现侧边导航栏功能
2018/09/10 Javascript
解决JavaScript中0.1+0.2不等于0.3问题
2018/10/23 Javascript
vue利用全局导航守卫作登录后跳转到未登录前指定页面的实例代码
2020/05/19 Javascript
[44:50]DOTA2上海特级锦标赛B组小组赛#2 VG VS Fnatic第二局
2016/02/26 DOTA
Python中使用第三方库xlrd来写入Excel文件示例
2015/04/05 Python
选择Python写网络爬虫的优势和理由
2019/07/07 Python
python @propert装饰器使用方法原理解析
2019/12/25 Python
英国高档时尚男装购物网站:MR PORTER
2016/08/09 全球购物
为智能设备设计个性化保护套网站:caseable
2017/01/05 全球购物
Oakley西班牙官方商店:太阳眼镜和男女运动服
2019/04/26 全球购物
护士自荐信范文
2013/12/15 职场文书
小学教师培训感言
2014/02/11 职场文书
《鸿门宴》教学反思
2014/04/22 职场文书
个人课题方案
2014/05/08 职场文书
人民调解员先进事迹材料
2014/05/08 职场文书
员工生日会策划方案
2014/06/14 职场文书
少先队工作总结2015
2015/05/13 职场文书
三好学生主要事迹材料
2015/11/03 职场文书
为什么中国式养孩子很累?
2019/08/07 职场文书
python获取带有返回值的多线程
2022/05/02 Python