浅谈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获取元素在浏览器中的绝对位置
Jul 24 Javascript
Grid得到选择行数据的方法总结
Jan 17 Javascript
js用正则表达式来验证表单(比较齐全的资源)
Nov 17 Javascript
两个select多选模式的选项相互移动(示例代码)
Jan 11 Javascript
JavaScript常用判断写法大全(推荐)
May 30 Javascript
AngularJS入门教程之ng-checked 指令详解
Aug 01 Javascript
Vue.js每天必学之内部响应式原理探究
Sep 07 Javascript
JS当前页面登录注册框,固定DIV,底层阴影的实例代码
Sep 29 Javascript
Angular2中Bootstrap界面库ng-bootstrap详解
Oct 18 Javascript
vue-cli的工程模板与构建工具详解
Sep 27 Javascript
详解element-ui设置下拉选择切换必填和非必填
Jun 17 Javascript
vue 实现强制类型转换 数字类型转为字符串
Nov 07 Javascript
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计算两个路径的相对路径
2013/06/14 PHP
php的sso单点登录实现方法
2015/01/08 PHP
php递归调用删除数组空值元素的方法
2015/04/28 PHP
CodeIgniter配置之autoload.php自动加载用法分析
2016/01/20 PHP
PHP实现将优酷土豆腾讯视频html地址转换成flash swf地址的方法
2017/08/04 PHP
javascript实现的在当前窗口中漂浮框的代码
2010/03/15 Javascript
浅谈javascript的数据类型检测
2010/07/10 Javascript
深入理解JavaScript系列(26):设计模式之构造函数模式详解
2015/03/03 Javascript
JS实现兼容性较好的随屏滚动效果
2015/11/09 Javascript
jQuery选择器实例应用
2017/01/05 Javascript
JavaScript实现简单生成随机颜色的方法
2017/09/21 Javascript
angular4自定义组件详解
2017/09/28 Javascript
vue地区选择组件教程详解
2018/05/04 Javascript
node微信开发之获取access_token+自定义菜单
2019/03/17 Javascript
jQuery操作attr、prop、val()/text()/html()、class属性
2019/05/23 jQuery
vue使用canvas实现移动端手写签名
2020/09/22 Javascript
JavaScript ES 模块的使用
2020/11/12 Javascript
[00:12]2018DOTA2亚洲邀请赛 sylar表现SOLO技艺
2018/04/06 DOTA
Python Sqlite3以字典形式返回查询结果的实现方法
2016/10/03 Python
flask中使用蓝图将路由分开写在不同文件实例解析
2018/01/19 Python
Python实现简单文本字符串处理的方法
2018/01/22 Python
python3实现点餐系统
2019/01/24 Python
Python3.5文件修改操作实例分析
2019/05/01 Python
Pandas分组与排序的实现
2019/07/23 Python
python实现人机五子棋
2020/03/25 Python
五分钟学会怎么用python做一个简单的贪吃蛇
2021/01/12 Python
解决img标签上下出现间隙的方法
2016/12/14 HTML / CSS
canvas版人体时钟的实现示例
2021/01/29 HTML / CSS
二年级数学教学反思
2014/01/21 职场文书
倡导文明标语
2014/06/16 职场文书
七一建党节演讲稿
2014/09/11 职场文书
学校政风行风评议心得体会
2014/10/21 职场文书
师德师风事迹材料
2014/12/20 职场文书
2016年寒假学习心得体会
2015/10/09 职场文书
《童年的发现》教学反思
2016/02/18 职场文书
Python列表的索引与切片
2022/04/07 Python