vuex 版本为
^2.3.1
,按照我本身的理解来整理vuex。html
这里应该很好理解。vue
更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似于事件:每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是咱们实际进行状态更改的地方,而且它会接受 state 做为第一个参数:vuex
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
方法:segmentfault
// 至关于就是一个特殊的调用事件方式来调用
store.commit('increment')
复制代码
能够向 store.commit
传入额外的参数,即 mutation 的 载荷(payload)api
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 能够传参数,而且参数最好以对象的方式来传。app
提交 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 例子:jsrun.net/VvqKp
例子里面会变成加11 !
既然 Vuex 的 store 中的状态是响应式的,那么当咱们变动状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也须要与使用 Vue 同样遵照一些注意事项:
Vue.set(obj, 'newProp', 123)
(沿用 vue 的方式)state.obj = { ...state.obj, newProp: 123 }
(先用扩展符号解构对象,而后赋值到新对象,由于对象在 js 里面是引用类型。)使用常量替代 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 必须是同步函数。
实质上任何在回调函数中进行的的状态的改变都是不可追踪的。因此须要在 actions 里面进行异步封装 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(namespace?: string, map: Array<string> | Object): Object
(官网 api 文档的格式)
首先:normalizeMap会将mutations格式化为一个数组:
function normalizeMap (map) {
// 判断是否数组,而且最终返回也是一个数组
return Array.isArray(map)
// 是数组就直接 map 循环
? map.map(key => ({ key, val: key }))
// 是对象就将 key拿出来,而后再进行 map 循环
: Object.keys(map).map(key => ({ 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
});
复制代码
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')
}
})
}
}
复制代码
this.$store.commit('increment')
的,有参数的话会自动加参数而已。这是 jsrun 的例子:jsrun.net/U6qKp
参考: