vuex 基本入门和使用(三)-关于 mutation

vuex 版本为^2.3.1,按照我自己的理解来整理vuex。

关于 mutation

这里应该很好理解。

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数


const store = new Vuex.Store({
state: { // 类似 vue 的 data
count: 1
},
mutations: { // 类似 vue 的 methods
increment (state) { // 这是一个回调函数
// 变更状态
state.count++
}
}
})

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用store.commit 方法:


// 相当于就是一个特殊的调用事件方式来调用
store.commit('increment')

提交载荷(Payload)

可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)


mutations: {
// 第一个参数是 state,第二个参数叫额外的参数,这里是n
increment (state, n) {
state.count += n
}
}
// 回调函数 increment 和参数10,后者是作为额外参数传入,n 就是10
store.commit('increment', 10)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:


mutations: {
increment (state, payload) {
// payload 作为一个对象,更加可读,统一对象形式调用
state.count += payload.amount
}
}
// 传入的是对象(即将额外的 mutation 参数以对象的方式传入)
store.commit('increment', {
amount: 10
})

这里总的来说就是说 mutations 可以传参数,并且参数最好以对象的方式来传。

对象风格的提交方式

提交 mutation 的另一种方式是直接使用包含 type 属性的对象:


// 这里也是传入一个对象,不过这个对象包含了 type 属性
store.commit({
type: 'increment',
amount: 10
})

这里只是一种提交 mutations 的方式,不必深究。

当使用这种对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:


mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
// vuex 会将这个对象分解,除了 type 之外的,依然会是作为额外参数传入
store.commit({
type: 'increment',
amount: 10
})

将整个对象传给 mutation后,vuex 会根据 type 参数识别到这是一个mutation 的载荷参数,然后自动填充 state 参数为第一位,第二位参数为传入的这个对象的第二位参数。

这是 jsrun 的 demo 例子:https://jsrun.net/VvqKp

例子里面会变成加11 !

Mutation 需遵守 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  • 最好提前在你的 store 中初始化好所有所需属性。
  • 当需要在对象上添加新属性时,你应该

    • 使用 Vue.set(obj, 'newProp', 123) (沿用 vue 的方式)
    • 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符我们可以这样写:state.obj = { ...state.obj, newProp: 123 }(先用扩展符号解构对象,然后赋值到新对象,因为对象在 js 里面是引用类型。)

使用常量替代 Mutation 事件类型

使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。


// mutation-types.js 放置常量的文件
export const SOME_MUTATION = 'SOME_MUTATION' // store.js
import Vuex from 'vuex'
// 单独导入了某个常量来测试这个用法
import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})

备注:es2015的计算属性名会使用中括号进行命名,中括号的方式允许我们使用变量或者在使用标识符时会导致语法错误的字符串直接量来定义属性,例如person["first name"],仅此而已。

我觉得,及早适应这种写法比较好,既能装逼又可以学到别人的高级技能。

Mutation 必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数。

实质上任何在回调函数中进行的的状态的改变都是不可追踪的。所以需要在 actions 里面进行异步封装 mutation 来实现异步。

在组件中提交 Mutation

你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。


import { mapMutations } from 'vuex' export default {
// ...
methods: {
// mapMutations 工具函数会将 store 中的 commit 方法映射到组件的 methods 中
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
  • ...es2015的扩展运算符,能够解构数组或者对象,这里是解构mapMutations对象。
  • mapMutations的写法和参数:mapMutations(namespace?: string, map: Array<string> | Object): Object(官网 api 文档的格式)

    • 第一个参数是模块的空间名称字符串,可以不填
    • 第二个参数是一个 map结构的对象,也可以是字符串数组
    • 返回的是一个对象
    • 了解更多 jsdoc 的格式标注,请参考:Use JSDoc: @type

关于...mapMutations

首先:normalizeMap会将mutations格式化为一个数组:


function normalizeMap (map) {
// 判断是否数组,并且最终返回也是一个数组
return Array.isArray(map)
// 是数组就直接 map 循环
? map.map(key =&gt; ({ key, val: key }))
// 是对象就将 key拿出来,然后再进行 map 循环
: Object.keys(map).map(key =&gt; ({ key, val: map[key] }))
}

例如传入的mutations 是一个数组,如下:


// 转换前
[
// 这是没额外参数的(没载荷)
'increment',
// 这是有额外参数的(有载荷)
'incrementBy'
]
// 那么被normalizeMap转换后:
// 即转换为{ key, val: key })
[
{
key, // key 是increment
val: key // val是increment
},
// 这里虽然说有额外参数传入,但是这个参数并没有在转换中处理
{
key, // key 是incrementBy
val: key // val是incrementBy
},
//.....
]

例如传入的mutations 是一个对象,如下:


// 转换前
{
addAlias: function(commit, playload) {
commit('increment')
commit('increment', playload)
}
}
// 那么被normalizeMap转换后:
// 即转换为{ key, val: key })
{
key, // key 是addAlias
val: map[key] // val 是对象的 key 属性的值,就是 function().....
}

然后看回去 vuex 的源代码关于mapMutations的部分:


// 参考 vuex 的源代码
var mapMutations = normalizeNamespace(function (namespace, mutations) {
var res = {};
// 被normalizeMap格式化后的mutations被 foreach 循环
normalizeMap(mutations).forEach(function (ref) {
var key = ref.key;
var val = ref.val; res[key] = function mappedMutation () {
// 拷贝载荷:复制额外参数到 args 数组
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ]; var commit = this.$store.commit;
// 先不管命名空间
//......
return typeof val === 'function'
// 是函数,则直接执行该函数,并将comit作为其第一个参数,arg仍然作为后续参数。
? val.apply(this, [commit].concat(args))
// 不是函数,则直接执行commit,参数是value和载荷组成的数组。
: commit.apply(this.$store, [val].concat(args))
};
});
return res
});
  • 和mapState的实现几乎完全一样,唯一的差别只有两点:

    • 提交mutaion时可以传递载荷,也可以不传,不管传不传,这里都会进行拷贝载荷。
    • 对于函数就会执行这个函数,因为这个函数里面其实就是一些 commit,而对于不是函数的内容就会直接进行 commit 操作,不过会绑定当前的this.$store作为作用域,也会传载荷的参数。

那么回归到实际转换效果,如下:


// 需要引入mapMutations才可以使用
import { mapMutations } from 'vuex' export default {
// ...
methods: {
...mapMutations('moduleName', [
// 将 `this.increment()` 映射为 `this.$store.commit('increment')`
'increment', // `mapMutations` 也支持载荷:
// 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
'incrementBy'
]), ...mapMutations('moduleName', {
// 将 `this.add()` 映射为 `this.$store.commit('increment')`
add: 'increment'
}), ...mapMutations('moduleName', {
addAlias: function(commit) {
//将 `this.addAlias()` 映射为 `this.$store.commit('increment')`
commit('increment')
}
})
}
}
  • increment和incrementBy其实是一样的,只是为了区分所以起了两个名字,可以看到他是直接转为为this.$store.commit('increment')的,有参数的话会自动加参数而已。
  • 其他就比较好理解了,结合之前看到的源码,就知道他的转换是分别处理

这是 jsrun 的例子:https://jsrun.net/U6qKp


参考:

原文地址:https://segmentfault.com/a/1190000012878534

vuex 基本入门和使用(三)-关于 mutation的更多相关文章

  1. Vuex 2 入门与提高。

    从计数器开始 让我们从一个简单的计数器,开始进入Vuex 的世界: 计数器应用的数据模型很简单:使用一个counter属性来表示计数器的 当前值就够了. 在Vue实例的created钩子 中,应用启动 ...

  2. Vuex的入门教程

    前言 在 Vue.js 的项目中,如果项目结构简单, 父子组件之间的数据传递可以使用  props 或者 $emit 等方式,详细点击这篇文章查看. 但是如果是大型项目,很多时候都需要在子组件之间传递 ...

  3. WCF入门教程(三)定义服务协定--属性标签

    WCF入门教程(三)定义服务协定--属性标签 属性标签,成为定义协议的主要方式.先将最简单的标签进行简单介绍,以了解他们的功能以及使用规则. 服务协定标识,标识哪些接口是服务协定,哪些操作时服务协定的 ...

  4. iOS开发-UI 从入门到精通(三)

    iOS开发-UI 从入门到精通(三)是对 iOS开发-UI 从入门到精通(一)知识点的综合练习,搭建一个简单地登陆界面,增强实战经验,为以后做开发打下坚实的基础! ※在这里我们还要强调一下,开发环境和 ...

  5. Docker入门教程(三)Dockerfile

    Docker入门教程(三)Dockerfile [编者的话]DockerOne组织翻译了Flux7的Docker入门教程,本文是系列入门教程的第三篇,介绍了Dockerfile的语法,DockerOn ...

  6. C语言细节——献给入门者(三)

    C语言细节——献给入门者(三) >>主题:关于强制类型转换 先来瞎扯下强制类型转换,c语言有很多数据类型,long,short,int,float,double,bool,char等等.当 ...

  7. SQLite 入门教程(三)好多约束 Constraints(转)

    转于: SQLite 入门教程(三)好多约束 Constraints 一.约束 Constraints 在上一篇随笔的结尾,我提到了约束, 但是在那里我把它翻译成了限定符,不太准确,这里先更正一下,应 ...

  8. Django入门实践(三)

    Django入门实践(三) Django简单应用 前面简单示例说明了views和Template的工作过程,但是Django最核心的是App,涉及到App则会和Model(数据库)打交道.下面举的例子 ...

  9. 爬虫入门系列(三):用 requests 构建知乎 API

    爬虫入门系列目录: 爬虫入门系列(一):快速理解HTTP协议 爬虫入门系列(二):优雅的HTTP库requests 爬虫入门系列(三):用 requests 构建知乎 API 在爬虫系列文章 优雅的H ...

随机推荐

  1. POJ 2661Factstone Benchmark(斯特林公式)

    链接:传送门 题意:一个人自命不凡,他从1960年开始每10年更新一次计算机的最长储存长数.1960年为4位,每10年翻一倍.给出一个年份y,问这一年计算机可以执行的n!而不溢出的最大n值 思路:如果 ...

  2. POJ 2774 Long Long Message (后缀数组+二分)

    题目大意:求两个字符串的最长公共子串长度 把两个串接在一起,中间放一个#,然后求出height 接下来还是老套路,二分出一个答案ans,然后去验证,如果有连续几个位置的h[i]>=ans,且存在 ...

  3. Javascript的jsonp原理

    Javascript的jsonp原理   首先JSON是一种基于文本的数据交换方式,或者叫做数据描述格式 当一个网页在请求JavaScript文件时则不受是否跨域的影响,凡是拥有”src”这个属性的标 ...

  4. Linux下的进程环境

    僵尸进程.孤儿进程.守护进程.进程组.会话.前台进程组.后台进程组 1,僵尸进程 子进程结束,父进程没有明确的答复操作系统内核:已收到子进程结束的消息.此时操作系统内核会一直保存该子进程的部分PCB信 ...

  5. HTML5与后台服务器的数据流动问题

    编辑中,尚未完稿...2017.7.14 1345 很多前端开发出来的HTML5可能对于后台开发者来说,并不是很清楚,也许像我一样一知半解.而且真的让人很糊涂的地方就是前端的JS如何与后端的数据库进行 ...

  6. 一个通用Makefile的编写

    作者:杨老师,华清远见嵌入式学院讲师. 我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件.如果我们用gcc去一个一个编译每一个源文件的 ...

  7. POJ 2906 数学期望

    开始时直接设了一个状态,dp[i][j]为发现i种bug,j个系统有bug的期望天数.但很错误,没能转移下去.... 看了题解,设状态dp[i][j]为已发现i种bug,j个系统有bug,到完成目标状 ...

  8. Oracle性能分析1:开启SQL跟踪和获取trace文件

    当Oracle查询出现效率问题时,我们往往须要了解问题所在,这样才干针对问题给出解决方式.Oracle提供了SQL运行的trace信息,当中包括了SQL语句的文本信息.一些运行统计,处理过程中的等待, ...

  9. Redis和Memcache和MongoDB简介及区别分析(整理)

    Redis和Memcache 一.Redis简介 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年 ...

  10. linux 终端提示符

    默认的当路径一长就难看得出奇. 我的设置: export PS1="|\W$>\[\e[0m\]" 最后效果就是|目录名$> 参考:https://www.cnblog ...