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

背景

在最近的一次需求开发过程中,有再次使用到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中修改状态“这个问题的分析,希望对大家有所帮助,也欢迎指正

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

(0)

相关推荐

  • 详解vuex中mapState,mapGetters,mapMutations,mapActions的作用

    在开始接触vuex框架的时候对那些state,action,mutation,getter等理解的还挺顺利的,然后突然出来一种加了一个前缀的mapState等,这样的就有点蒙圈了...特别是官方的文档并没有给除详细的说明跟例子...然后就自己慢慢理解了一下.其实也就是一个重命名而已...以下就是例子,希望能帮助理解: 在store中代码 import Vuex from 'vuex' import Vue from 'vue' Vue.use(Vuex); const store = new V

  • vuex中的 mapState,mapGetters,mapActions,mapMutations 的使用

    一.介绍 vuex里面的四大金刚:State, Mutations,Actions,Getters (上次记得关于vuex笔记 http://www.jb51.net/article/138229.htm,是一个简单的应用:这是一些简单的vue的小组件方法: http://www.jb51.net/article/138230.htm) 何为四大金刚? 1.State (这里可以是 小写的 state,跟官网保持一致,采用大写,因为个人习惯,后面的代码介绍采用小写) vuex的状态管理,需要依赖

  • VUEX-action可以修改state吗

    首先回顾下vuex,官网图如下 Vuex 的 store 中的状态的唯一方法是提交 mutation(mutation类似于事件且必须是同步函数) action 提交的是 mutation,而不是直接变更状态且可以包含任意异步操作(Action通过 store.dispatch 方法触发) 一幅图看清只能通过mutation修改state的原因 commit函数源码如下 commit (_type, _payload, _options) { // check object-style comm

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

    背景 在最近的一次需求开发过程中,有再次使用到Vuex,在状态更新这一方面,我始终遵循着官方的"叮嘱",谨记"一定不要在action中修改state,而是要在mutation中修改":于是我不禁产生了一个疑问:Vuex为什么要给出这个限制,它是基于什么原因呢?带着这个疑问我查看Vuex的源码,下面请大家跟着我的脚步,来一起揭开这个问题的面纱. 一起阅读源码吧~ 1.首先我们可以在src/store.js这个文件的Store类中找到下面这段代码 // ... this

  • 浅谈vuex中store的命名空间

    为了防止store变的过于臃肿,我们可以为store注册模块,模块默认是属于全局命名空间的,也就是说当用下列代码分发action时,任意模块只要action中有addNews,就会得到执行 this.$store.dispatch('addNews') 有时候这种情况会不是我们想要的,为此我们可以为模块指定命名空间,相当于把它封装起来,这样在像上面那样分发action,具有命名空间的模块中的addNews函数就不会执行 当然你也可以指定位于特定的命名空间中的模块执行addNews这个函数,方法如

  • 浅谈vuex的基本用法和mapaction传值问题

    vuex的理论知识就不多提了,官网上已经有明确的讲解. 用一个简单的例子来描述一下基本的用法: 第一步:npm install vuex –save-dev 第二步:在目录中创建store目录配置管理状态 //store/index.js /** * Created by zhaohuan on 2017/7/13. */ import Vue from 'vue' import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.St

  • 浅谈Vuex的this.$store.commit和在Vue项目中引用公共方法

    1.在Vue项目中引用公共方法 作为一个新人小白,在使用vue的过程中,难免会遇到很多的问题,比如某个方法在很多组件中都能用的上,如果在每个组件上都去引用一次的话,会比较麻烦,增加代码量.怎么做比较好呢,话不多说直接看代码把 首先 要在main.js中引入公共js.然后,将方法赋在Vue的原型链上. 像图中这样. 然后在需要的组件上去引入这个方法 mouted (){ //调用方法 this.common.login(); } /**然后公共方法里写一段简单的代码*/ export defaul

  • 浅谈为什么MySQL不建议delete删除数据

    前言 我负责的有几个系统随着业务量的增长,存储在MySQL中的数据日益剧增,我当时就想现在的业务方不讲武德,搞偷袭,趁我没反应过来把很多表,很快,很快啊都打到了亿级别,我大意了,没有闪,这就导致跟其Join的表的SQL变得很慢,对的应用接口的response time也变长了,影响了用户体验. 事后我找到业务方,我批评了他们跟他们说要讲武德,连忙跟我道歉,这个事情才就此作罢,走的时候我对他们说下次不要这样了,耗子尾汁,好好反思. 骂归骂,事情还是得解决,时候我分析原因发现,发现有些表的数据量增长

  • 浅谈如何使用python抓取网页中的动态数据实现

    我们经常会发现网页中的许多数据并不是写死在HTML中的,而是通过js动态载入的.所以也就引出了什么是动态数据的概念,动态数据在这里指的是网页中由Javascript动态生成的页面内容,是在页面加载到浏览器后动态生成的,而之前并没有的. 在编写爬虫进行网页数据抓取的时候,经常会遇到这种需要动态加载数据的HTML网页,如果还是直接从网页上抓取那么将无法获得任何数据. 今天,我们就在这里简单聊一聊如何用python来抓取页面中的JS动态加载的数据. 给出一个网页:豆瓣电影排行榜,其中的所有电影信息都是

  • 浅谈do {...} while (0) 在宏定义中的作用

    如果你是一名C程序员,你肯定很熟悉宏,它们非常强大,如果正确使用可以让你的工作事半功倍.然而,如果你在定义宏时很随意没有认真检查,那么它们可能使你发狂,浪费N多时间.在很多的C程序中,你可能会看到许多看起来不是那么直接的较特殊的宏定义. 下面就是一个例子: #define __set_task_state(tsk, state_value) \ do { (tsk)->state = (state_value); } while (0) 在Linux内核和其它一些著名的C库中有许多使用do{..

  • 浅谈Java 将图片打包到jar中的路径问题

    Eclipse使用导出Jar包后打开加载不了图像? 出现这种问题的原因大多是因为路径的问题,往往是在项目中运行正常,但是一旦打包成Jar后就不能正常显示了,下面总结一下解决此类问题的方法: 一.通过使用外部资源文件的方式解决 把项目打包生成Jar后,再新建一文件夹,把项目中所有使用的图片文件和生成Jar文件放在该目录下,只要代码对图片文件的引用路径正确,如:ImageIcon image=new ImageIcon(SwingResourceManager.getImage("icons/log

  • 浅谈如何使用vb.net从数据库中提取数据

    1.设置从Model中的Sub Main 启动 2.程序结构 3.Model1 Imports System.Windows.Forms.Application Module Module1 Sub Main() 'form1 是测试多文档窗口 'Dim frm1 As New Form1() 'frm1.Show() Dim formStudentSysMain As New FormStudentSysMain() formStudentSysMain.Show() Do While Tru

  • 浅谈java运用注解实现对类中的方法检测的工具

    创建自定义注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { } 建立测试类 public class UserTest { @Test public void testInsert() { User user = null; System.out.println(user.getUsername()); } @Test public void testQuery(

随机推荐