浅谈vuex为什么不建议在action中修改state


Posted in Javascript onFebruary 02, 2020

背景

在最近的一次需求开发过程中,有再次使用到Vuex,在状态更新这一方面,我始终遵循着官方的“叮嘱”,谨记“一定不要在action中修改state,而是要在mutation中修改”;于是我不禁产生了一个疑问:Vuex为什么要给出这个限制,它是基于什么原因呢?带着这个疑问我查看Vuex的源码,下面请大家跟着我的脚步,来一起揭开这个问题的面纱。

一起阅读源码吧~

1.首先我们可以在src/store.js这个文件的Store类中找到下面这段代码

// ...
this.dispatch = function boundDispatch (type, payload) {
 return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
 return commit.call(store, type, payload, options)
}
// ...

上面是Vuex两个最核心的API:dispatch & commit,它们是分别用来提交action和mutation的

那么既然我们今天的目的是为了“了解为什么不能在action中修改state”,所以我们就先看看mutation是怎样修改state的,然而mutation是通过commit提交的,所以我们先看一下commit的内部实现

commit&mutation

2.commit方法的核心代码大致如下:

commit (_type, _payload, _options) {
  // ...
  this._withCommit(() => {
   entry.forEach(function commitIterator (handler) {
    handler(payload)
   })
  })
  // ...
}

不难看出,Vuex在commit(提交)某种类型的mutation时,会先用_withCommit包裹一下这些mutation,即作为参数传入_withCommit;那么我们来看看_withCommit的内部实现(ps:这里之所以说”某种类型的mutation“,是因为Vuex的确支持声明多个同名的mutation,不过前提是它们在不同的namespace下;action同理)

3._withCommit方法的代码如下:

_withCommit (fn) {
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
 }

是的,你没有看错,它真的只有4行代码;这里我们注意到有一个标志位_committing,在执行fn前,这个标志位会被置为true,这个点我们先记下,一会儿会用到

4.接下来,我要为大家要介绍的是resetStoreVM这个函数,它的作用是初始化store,它首次被执行是在Store的构造函数中

function resetStoreVM (store, state, hot) {
 // ...
 if (store.strict) {
  enableStrictMode(store)
 }
// ...
}

在这里有一处需要我们注意:resetStoreVM对strict(是否启用严格模式)做了判断,这里假设我们启用严格模式,那么就会执行enableStrictMode这个函数,下面继续来看看它的内部实现

function enableStrictMode (store) {
 store._vm.$watch(function () { return this._data.$$state }, () => {
  if (process.env.NODE_ENV !== 'production') {
   assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
  }
 }, { deep: true, sync: true })
}

这里对Vue组件实例的state做了监听,一旦监听到变化,就会执行asset(断言),它断言的恰巧就是刚才我让大家记住的那个_committing标志位,那么我们再来看看这个asset做了些什么

5.asset方法在src/util.js这个文件中

export function assert (condition, msg) {
 if (!condition) throw new Error(`[vuex] ${msg}`)
}

这个方法很简单,就是判断第一个参数是否为truly值,如果不为真,就抛出一个异常
到此,我们已简单地了解了commit和mutation的逻辑,下面再来看看dispatch和action

dispatch&action

6.dispatch代码大致如下:

dispatch (_type, _payload) {
  const {
   type,
   payload
  } = unifyObjectStyle(_type, _payload)

  const action = { type, payload }
  const entry = this._actions[type]

 // ...
  const result = entry.length > 1
   ? Promise.all(entry.map(handler => handler(payload)))
   : entry[0](payload)
 // ...
 }

这里我们注意到,当某种类型的action只有一个声明时,action的回调会被当作普通函数执行,而当如果有多个声明时,它们是被视为Promise实例,并且用Promise.all执行,总所周知,Promise.all在执行Promise时是不保证顺序的,也就是说,假如有3个Promise实例:P1、P2、P3,它们3个之中不一定哪个先有返回结果,那么我们仔细思考一下:如果同时在多个action中修改了同一个state,那会有什么样的结果?

其实很简单,我们在多个action中修改同一个state,因为很有可能每个action赋给state的新值都有所不同,并且不能保证最后一个有返回结果action是哪一个action,所以最后赋予state的值可能是错误的

那么Vuex为什么要使用Promise.all执行action呢?其实也是出于性能考虑,这样我们就可以最大限度进行异步操作并发
眼尖的同学可能已经发现在dispatch中并没有看到_committing的身影,就是Vuex对action修改state的限制:当action想要修改state时,因为_committing没有事先被置为true,而导致asset阶段无法通过

但这个限制只限于开发阶段,因为在enableStrictMode函数中,Webpack加入了对环境的判断,如果不是生产环境(也就是开发环境)才会输出asset(断言)这行代码

function enableStrictMode (store) {
 store._vm.$watch(function () { return this._data.$$state }, () => {
  if (process.env.NODE_ENV !== 'production') {
   assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
  }
 }, { deep: true, sync: true })
}

那么也就是说如果你强行在生产环境中用action修改state,Vuex也不会阻止你,它可能仅仅是给你一个警告;而且按道理来说,如果我们能够保证同一类型的action只有一个声明,那么无论是使用action还是mutation来修改state结果都是一样的,因为Vuex针对这种情况,没有使用Promise.all执行action,所以也就不会存在返回结果先后问题

dispatch (_type, _payload) {
  // ...
  const result = entry.length > 1
   ? Promise.all(entry.map(handler => handler(payload)))
   : entry[0](payload)
  // ...
 }

但是凡是靠人遵守的约定都是不靠谱的,所以我们在平时使用Vuex时,最好还是遵守官方的约束,否则线上代码有可能出现bug,这不是我们所期望的。

结束语

Vuex这一限制其实也是出于代码设计考虑,action和mutation各司其事,本质上也是遵守了“单一职责”原则。以上就是我对“Vuex为什么不允许在action中修改状态“这个问题的分析,希望对大家有所帮助,也欢迎指正

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js 未结束的字符串常量错误解决方法
Jun 13 Javascript
用jquery方法操作radio使其默认选项是否
Sep 10 Javascript
js 实现浏览历史记录示例
Apr 20 Javascript
jQuery获取iframe的document对象的方法
Oct 10 Javascript
在JavaScript应用中实现延迟加载的方法
Jun 25 Javascript
JS控制TreeView的结点选择
Nov 11 Javascript
原生JS简单实现ajax的方法示例
Nov 29 Javascript
node.js程序作为服务并在windows下开机自启动(用forever)
Mar 29 Javascript
jQuery基于闭包实现的显示与隐藏div功能示例
Jun 09 jQuery
vue tab切换,解决echartst图表宽度只有100px的问题
Jul 19 Javascript
使用Canvas绘制一个游戏人物属性图
Mar 25 Javascript
vue3语法糖内的defineProps及defineEmits
Apr 14 Vue.js
vuex+axios+element-ui实现页面请求loading操作示例
Feb 02 #Javascript
vue实现的封装全局filter并统一管理操作示例
Feb 02 #Javascript
node 版本切换的实现
Feb 02 #Javascript
vue路由缓存的几种实现方式小结
Feb 02 #Javascript
vue简单封装axios插件和接口的统一管理操作示例
Feb 02 #Javascript
vue实现路由不变的情况下,刷新页面操作示例
Feb 02 #Javascript
JQuery事件委托(适用于给动态生成的脚本元素添加事件)
Feb 01 #jQuery
You might like
php获取网页内容方法总结
2008/12/04 PHP
php判断数组元素中是否存在某个字符串的方法
2014/06/14 PHP
两个php日期控制类实例
2014/12/09 PHP
PHP读取配置文件类实例(可读取ini,yaml,xml等)
2015/07/28 PHP
thinkPHP自动验证机制详解
2016/12/05 PHP
php加密之discuz内容经典加密方式实例详解
2017/02/04 PHP
PHP实现二叉树深度优先遍历(前序、中序、后序)和广度优先遍历(层次)实例详解
2018/04/20 PHP
用javascript实现的支持lrc歌词的播放器
2007/05/17 Javascript
在次封装easyui-Dialog插件实现代码
2010/11/14 Javascript
jquery图片轮播插件仿支付宝2013版全屏图片幻灯片
2014/04/03 Javascript
js中的setInterval和setTimeout使用实例
2014/05/09 Javascript
jQuery实现仿腾讯微博滑出效果报告每日天气的方法
2015/05/11 Javascript
基于javascript实现图片左右切换效果
2016/01/25 Javascript
JS获得多个同name 的input输入框的值的实现方法
2017/01/09 Javascript
JS可断点续传文件上传实现代码解析
2020/07/30 Javascript
ant-design-vue 时间选择器赋值默认时间的操作
2020/10/27 Javascript
[51:26]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#3Secret VS OG第二局
2016/03/03 DOTA
[02:27]2018DOTA2亚洲邀请赛趣味视频之钓鱼大赛 谁是垂钓冠军?
2018/04/05 DOTA
解决py2exe打包后,总是多显示一个DOS黑色窗口的问题
2019/06/21 Python
python实现控制电脑鼠标和键盘,登录QQ的方法示例
2019/07/06 Python
python爬虫实现POST request payload形式的请求
2020/04/30 Python
美国最大网上鞋店:Zappos
2016/07/25 全球购物
介绍一下Linux内核的排队自旋锁
2014/08/27 面试题
启动一个线程是用run()还是start()
2016/12/25 面试题
车间操作工岗位职责
2013/12/19 职场文书
营销总经理岗位职责
2014/02/02 职场文书
揭牌仪式策划方案
2014/05/28 职场文书
委托书格式
2014/08/01 职场文书
离婚协议书范本样本
2014/08/19 职场文书
党的群众路线教育实践活动个人对照检查剖析材料
2014/09/23 职场文书
亲属关系公证书样本
2015/01/23 职场文书
2015年领导干部廉洁自律工作总结
2015/05/26 职场文书
2016年优秀教师先进事迹材料
2016/02/26 职场文书
2019毕业论文致谢词
2019/06/24 职场文书
MongoDB数据库的安装步骤
2021/06/18 MongoDB
MySQL数据库查询之多表查询总结
2022/08/05 MySQL