Vuex中的核心方法
Vuex中的核心方法
Vuex是一个专为Vue.js应用程序开发的状态管理模式,其采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。每一个Vuex应用的核心就是store仓库,store基本上就是一个容器,它包含着你的应用中大部分的状态state。
描述
在大量的业务场景下,不同的模块组件之间确实需要共享数据,也需要对其进行修改操作。也就引发软件设计中的矛盾:模块组件之间需要共享数据和数据可能被任意修改导致不可预料的结果。为了解决其矛盾,软件设计上就提出了一种设计和架构思想,将全局状态进行统一的管理,并且需要获取、修改等操作必须按我设计的套路来,就好比马路上必须遵守的交通规则,右行斑马线就是只能右转一个道理,统一了对全局状态管理的唯一入口,使代码结构清晰、更利于维护。状态管理模式从软件设计的角度,就是以一种统一的约定和准则,对全局共享状态数据进行管理和操作的设计理念。你必须按照这种设计理念和架构来对你项目里共享状态数据进行CRUD,所以所谓的状态管理模式就是一种软件设计的一种架构模式。
关于Vuex的五个核心概念,在这里可以简单地进行总结:
state: 基本数据。getters: 从基本数据派生的数据。mutations: 提交更改数据的方法,同步操作。actions: 像一个装饰器,包裹mutations,使之可以异步。modules: 模块化Vuex。
State
Vuex使用单一状态树,即用一个对象就包含了全部的状态数据,state作为构造器选项,定义了所有我们需要的基本状态参数,也就是说state便是唯一数据源SSOT,同样每个应用将仅仅包含一个store实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。此外单状态树和模块化并不冲突,我们仍然可以将状态和状态变更事件分布到各个子模块中。使用Vuex并不意味着你需要将所有的状态放入Vuex,虽然将所有的状态放到Vuex会使状态变化更显式和易调试,但也会使代码变得冗长和不直观,如果有些状态严格属于单个组件,最好还是作为组件的局部状态。
在Vue组件中获得Vuex状态
从store实例中读取状态最简单的方法就是在计算属性中返回某个状态,由于Vuex的状态存储是响应式的,所以在这里每当store.state.count变化的时候,都会重新求取计算属性,进行响应式更新。
const store = new Vuex.Store({
state: {
count: 0
}
})
const vm = new Vue({
//..
store,
computed: {
count: function(){
return this.$store.state.count;
}
},
//..
})
mapState辅助函数
mapState函数返回的是一个对象,当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余,为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性。
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from "vuex";
export default {
// ...
computed: mapState({
// 箭头函数
count: state => state.count,
// 传字符串参数 count 等同于 state => state.count
countAlias: "count",
// 使用 this
countPlusLocalState: function(state) {
return state.count + this.localCount;
}
})
// ...
}
如果当前组件中还有局部计算属性需要定义,通常可以使用对象展开运算符...将此对象混入到外部对象中。
import { mapState } from "vuex";
export default {
// ...
computed: {
localComputed: function() { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
// ...
}
// ...
}
Getter
getters即从store的state中派生出的状态,例如我们需要对列表进行过滤并计数,如果有多个组件需要用到某个属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它,这两种方式无论哪种方式都不是很理想。而Vuex允许我们在store中定义getter(可以认为是store的计算属性),就像计算属性一样getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
访问getters
getters接收state作为其第一个参数,接受其他getters作为第二个参数,如不需要则第二个参数可以省略,与state一样,我们也可以通过Vue的Computed获得Vuex的getters。
const store = new Vuex.Store({
state: {
count:0
},
getters: {
// 单个参数
countDouble: function(state){
return state.count * 2
},
// 两个参数
countDoubleAndDouble: function(state, getters) {
return getters.countDouble * 2
}
}
})
const vm = new Vue({
//..
store,
computed: {
count: function(){
return this.$store.state.count;
},
countDouble: function(){
return this.$store.getters.countDouble;
},
countDoubleAndDouble: function(){
return this.$store.getters.countDoubleAndDouble;
}
},
//..
})
mapGetters辅助函数
mapGetters辅助函数是将store中的getters映射到局部计算属性,与state类似。
import { mapGetters } from "vuex";
export default {
// ...
computed: {
// 使用对象展开运算符将 getters 混入 computed 对象中
...mapGetters([
"countDouble",
"CountDoubleAndDouble",
//..
]),
...mapGetters({
// 映射 this.double 为 store.getters.countDouble
double: "countDouble"
})
}
// ...
}
Mutation
提交mutation是更改Vuex中的store中的状态的唯一方法,mutation必须是同步的,如果要异步需要使用action。
定义mutation
每个mutation都有一个字符串的事件类型type和一个回调函数handler,这个回调函数就是我们实际进行状态更改的地方,并且它会接受state作为第一个参数,提交载荷作为第二个参数(提交荷载在大多数情况下应该是一个对象),提交荷载也可以省略的。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 无提交荷载
increment: function(state) {
state.count++;
},
// 提交荷载
incrementN: function(state, payload) {
state.count += payload.n;
}
}
})
你不能直接调用一个mutation handler,这个选项更像是事件注册,当触发一个类型为increment的mutation时,调用此函数,要唤醒一个mutation handler,你需要以相应的type调用store.commit方法。
//无提交荷载
this.$store.commit("increment");
//提交荷载
this.$store.commit("incrementN", { n: 100 });
Mutations需遵守Vue的响应规则
既然Vuex的store中的状态是响应式的,那么当我们变更状态时,监视状态的Vue组件也会自动更新,这也意味着Vuex中的mutation也需要与使用Vue一样遵守一些注意事项:
- 最好提前在你的
store中初始化好所有所需属性。 - 当需要在对象上添加新属性时,应该使用
Vue.set(obj, "newProp", 1), 或者以新对象替换老对象,例如state.obj = { ...state.obj, newProp: 1 }。
Mutation必须是同步函数
一条重要的原则就是mutation必须是同步函数,假如我们正在debug一个app并且观察devtool中的mutation日志,每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照,然而如果在mutation中使用异步函数中的回调让这不可能完成,因为当mutation触发的时候,回调函数还没有被调用,devtools不知道什么时候回调函数实际上被调用,实质上任何在回调函数中进行的状态的改变都是不可追踪的。
在mutation中混合异步调用会导致你的程序很难调试,当你调用了两个包含异步回调的mutation来改变状态,你无法知道什么时候回调和哪个先回调,这就是为什么要区分Mutation和Action这两个概念,在Vuex中,mutation都是同步事务,任何由提交的key导致的状态变更都应该在此刻完成。
mapMutations辅助函数
与其他辅助函数类似,你可以在组件中使用this.$store.commit("xxx")提交mutation,或者使用mapMutations辅助函数将组件中的methods映射为store.commit调用。
import { mapMutations } from "vuex";
export default {
//..
methods: {
...mapMutations([
"increment" // 映射 this.increment() 为 this.$store.commit("increment")
]),
...mapMutations({
add: "increment" // 映射 this.add() 为 this.$store.commit("increment")
})
}
// ...
}
Action
Action类似于mutation,不同在于Action提交的是mutation,而不是直接变更状态,而且Action可以包含任意异步操作。
注册actions
Action函数接受一个与store实例具有相同方法和属性的context对象,因此你可以调用context.commit提交一个mutation,或者通过context.state和context.getters来获取state和getters。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: function(state) {
state.count++;
}
},
actions: {
increment: function(context) {
setInterval(() => context.commit("increment"), 1000);
}
}
})
分发actions
Action通过store.dispatch方法触发,同样也支持以载荷方式和对象方式进行分发。
// 分发
this.$store.dispatch("increment");
// 以载荷形式分发
store.dispatch("incrementN", { n: 10 });
// 以对象形式分发
store.dispatch({ type: "incrementN", n: 10 });
mapActions辅助函数
使用mapActions辅助函数可以将组件的methods映射为store.dispatch调用。
import { mapActions } from "vuex";
export default {
//..
methods: {
...mapActions([
"incrementN" //映射 this.incrementN() 为 this.$store.dispatch("incrementN")
]),
...mapActions({
add: "incrementN" //映射 this.add() 为 this.$store.dispatch("incrementN")
})
}
// ...
}
组合Action
Action通常是异步的,在一些场景下我们需要组合Action用以处理更加复杂的异步流程,store.dispatch可以处理被触发的action的处理函数返回的Promise,并且store.dispatch仍旧返回Promise。一个store.dispatch在不同模块中可以触发多个action函数,在这种情况下,只有当所有触发函数完成后,返回的Promise才会执行。
// ...
actions: {
actionA: function({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit("someMutation");
resolve();
}, 1000)
})
}
}
// ...
// 在触发Actions时
// ...
store.dispatch("actionA").then(() => {
// ...
})
// ...
// 在另外一个 action 中
// ...
actions: {
// ...
actionB: function({ dispatch, commit }) {
return dispatch("actionA").then(() => {
commit("someOtherMutation");
})
}
}
// ...
// 使用 async/await
// 当然此时getData()和getOtherData()需要返回Promise
actions: {
actionA: async function({ commit }) {
commit("gotData", await getData());
},
actionB: async function({ dispatch, commit }) {
await dispatch("actionA");
commit("gotOtherData", await getOtherData());
}
}
// ...
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store对象就有可能变得相当臃肿,为了解决以上问题,Vuex允许我们将store分割成模块。
模块分割
当进行模块分割时,每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块,即从上至下进行同样方式的分割。
const moduleA = {
state: () => ({ /* ... */ }),
mutations: { /* ... */ },
actions: { /* ... */ },
getters: { /* ... */ }
}
const moduleB = {
state: () => ({ /* ... */ }),
mutations: { /* ... */ },
actions: { /* ... */ }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
模块的局部状态
对于模块内部的mutation和getter,接收的第一个参数是模块的局部状态,对于模块内部的getter,根节点状态会作为第三个参数。
const moduleA = {
state: { count: 0 },
mutations: {
increment: function(state) {
// state 模块的局部状态
state.count++;
}
},
getters: {
doubleCount: function(state) {
return state.count * 2
},
sumWithRootCount: function(state, getters, rootState) {
return state.count + rootState.count;
}
}
}
同样对于模块内部的action,局部状态通过context.state暴露出来,根节点状态则为context.rootState。
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum: function({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit("increment");
}
}
}
}
命名空间
默认情况下,模块内部的action、mutation和getter是注册在全局命名空间的——这样使得多个模块能够对同一mutation或action作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加namespaced: true的方式使其成为带命名空间的模块,当模块被注册后,它的所有getter、action及mutation都会自动根据模块注册的路径调整命名。
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin: function() { ... } // -> getters['account/isAdmin']
},
actions: {
login: function() { ... } // -> dispatch('account/login')
},
mutations: {
login: function() { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile: function() { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular: function() { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
启用了命名空间的getter和action会收到局部化的getter,dispatch和commit。换言之,你在使用模块内容module assets时不需要在同一模块内额外添加空间名前缀,更改namespaced属性后不需要修改模块内的代码。
如果你希望使用全局state和getter,rootState和rootGetters会作为第三和第四参数传入getter,也会通过context对象的属性传入action。若需要在全局命名空间内分发action或提交mutation,将{ root: true }作为第三参数传给dispatch或commit即可。
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter(state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> "foo/someOtherGetter"
rootGetters.someOtherGetter // -> "someOtherGetter"
},
someOtherGetter: state => { /* ... */ }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> "foo/someGetter"
rootGetters.someGetter // -> "someGetter"
dispatch("someOtherAction") // -> "foo/someOtherAction"
dispatch("someOtherAction", null, { root: true }) // -> "someOtherAction"
commit("someMutation") // -> "foo/someMutation"
commit("someMutation", null, { root: true }) // -> "someMutation"
},
someOtherAction(ctx, payload) { /* ... */ }
}
}
}
若需要在带命名空间的模块注册全局action,你可添加root: true,并将这个action的定义放在函数handler中。
{
actions: {
someOtherAction({ dispatch }) {
dispatch("someAction")
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler(namespacedContext, payload) { /* ... */ } // -> "someAction"
}
}
}
}
}
当使用mapState、mapGetters、mapActions和mapMutations这些函数来绑定带命名空间的模块时,写起来可能比较繁琐,对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。或者你可以通过使用createNamespacedHelpers创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数
// ...
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
"some/nested/module/foo", // -> this["some/nested/module/foo"]()
"some/nested/module/bar" // -> this["some/nested/module/bar"]()
])
}
// ...
// ...
computed: {
...mapState("some/nested/module", {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions("some/nested/module", [
"foo", // -> this.foo()
"bar" // -> this.bar()
])
}
// ...
// ...
import { createNamespacedHelpers } from "vuex"
const { mapState, mapActions } = createNamespacedHelpers("some/nested/module")
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
"foo",
"bar"
])
}
}
// ...
模块动态注册
在store创建之后,你可以使用store.registerModule方法注册模块,之后就可以通过store.state.myModule和store.state.nested.myModule访问模块的状态。模块动态注册功能使得其他Vue插件可以通过在store中附加新模块的方式来使用Vuex管理状态。例如vuex-router-sync插件就是通过动态注册模块将vue-router和vuex结合在一起,实现应用的路由状态管理。你也可以使用store.unregisterModule(moduleName)来动态卸载模块,注意你不能使用此方法卸载静态模块,即创建store时声明的模块。此外你可以通过store.hasModule(moduleName)方法检查该模块是否已经被注册到store。
import Vuex from "vuex";
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule("myModule", {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(["nested", "myModule"], {
// ...
})
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://vuex.vuejs.org/zh/
https://www.jianshu.com/p/1fdf9518cbdf
https://www.jianshu.com/p/29467543f77a
https://juejin.cn/post/6844903624137523213
https://segmentfault.com/a/1190000024371223
https://github.com/Hibop/Hibop.github.io/issues/45
Vuex中的核心方法的更多相关文章
- vuex 中五大核心以及map函数的应用
什么是vuex? 我理解的vuex就是数据和状态的管理 如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex) 五大核心: const store = new Vuex.Store({ ...
- vuex中的辅助函数 mapState,mapGetters, mapActions, mapMutations
1.导入辅助函数 导入mapState可以调用vuex中state的数据 导入mapMutations可以调用vuex中mutations的方法 四个辅助函数 各自对应自己在vuex上的自己 2.ma ...
- vuex中mapGetters的使用及简单实现原理
一.项目中的mapGetters在Vue项目的开发过程中必然会使用到vuex,对vue项目公用数据进行管理,从而解决组件之间数据相互通信的问题,如果不使用vuex,那么一些非父子组件之间的数据通信将会 ...
- jQuery编程中的一些核心方法简介
调用 jQuery 对象的方法很简单: $('h1').remove(); 大多数 jQuery 方法都是像上面这样被调用的,这些方法都位于 $.fn 命名空间内,这些方法称为 jQuery 对象方法 ...
- VS2012中使用CEGUI项目发布到XP平台的问题(核心方法就一句话。“你项目使用的所有外部依赖库都用/MT编译。”)
接着上一篇文章,详细说说如何把一个带CEGUI的项目发布到XP平台. 这个问题纠缠了我好几天.这里把详细解决思路记下来.有同样问题的朋友可以少走很多弯路. 核心方法就一句话.“你项目使用的所有外部依赖 ...
- 【vue】vue +element 搭建项目,vuex中的store使用
概述: 每一个 Vuex 应用的核心就是 store(仓库).“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state).Vuex 和单纯的全局对象有以下两点不同: Vuex 的 ...
- 【zepto学习笔记01】核心方法$()
前言 我们移动端基本使用zepto了,而我也从一个小白变成稍微靠谱一点的前端了,最近居然经常要改到zepto源码但是,我对zepto不太熟悉,其实前端水准还是不够,所以便私下偷偷学习下吧,别被发现了 ...
- zepto源码--核心方法(类数组相关)--学习笔记
从这篇起,在没有介绍到各类插件之前,后面将陆续介绍zepto对外暴露的核心方法.即$.fn={}里面的所有方法的介绍.会配合zepto的API进行介绍. 其实前面已经介绍了几个,如width,heig ...
- 理解Lucene索引与搜索过程中的核心类
理解索引过程中的核心类 执行简单索引的时候需要用的类有: IndexWriter.Directory.Analyzer.Document.Field 1.IndexWriter IndexWr ...
- html 5 本地数据库(Web Sql Database)核心方法openDatabase、transaction、executeSql 详解
Web SQL数据库API实际上不是HTML5规范的组成部分,而是单独的规范.它通过一套API来操纵客户端的数据库.Safari.Chrome. Firefox.Opera等主流浏览器都已经支持Web ...
随机推荐
- Laravel - 修改时间戳
将 config/app.php文件下将timezone的值改成Asia/Shanghai即可
- Laravel : Eloquent 新增
public function ormCreate() { # 1. 使用模型新增 ->save() /* ...
- Nginx reload重新加载配置文件的异常情况
Nginx reload重新加载配置文件的异常情况 背景 临近年底, 很多业务工作量都上来了. 今天同事告知, nginx的log 里面出现了大量的 too many openfiles 的提示信息. ...
- [转帖]Oracle11g实现只读表方法
1.1 ALTER TABLE tab_name READ ONLY 参考:https://www.cnblogs.com/chinas/p/8440460.html Oracle 11g开始支持设置 ...
- [转帖]renice和nice
https://www.cnblogs.com/qiynet/p/17555881.html 将行程 id 为 987 及 32 的行程与行程拥有者为 daemon 及 root 的优先序号码加 1 ...
- 银河麒麟不同架构获取rpm包的方法
银河麒麟不同架构获取rpm包的方法 背景 随着信创和网络安全越来越重要 现阶段国产化的软硬件部署越来越多. 很多时候现场有很多国产化的设备.不同架构.不同版本. 还不能上网, 无法获取对应的安装介质. ...
- [转帖]Nacos 获取配置时启用权限认证
默认情况下获取 Nacos 中的配置是不需要权限认证的, 这个估计是由其使用场景决定的(绝大多数都是仅内网可访问). 今天调查了下如何在获取配置时增加权限验证以提高其安全性. 1. 启用 Nacos ...
- [转帖]gdb 常用命令
https://www.cnblogs.com/xvic/p/15997498.html 栈信息 不管是操作转储文件还是用GDB设置断点进行调试,都可以输入 (gdb)bt 打印栈内容进行查看.一般的 ...
- [转帖]第5章 WINDOWS PE/COFF
https://www.jianshu.com/p/35db9df2514f?utm_campaign=maleskine&utm_content=note&utm_medium=se ...
- Windows命令行查看相关信息
Windows命令行查看相关信息 查看网络相关 查看网络相关 netstat -ano |findstr -v 127 |findstr -v 10.110 |findstr -v 10.6 |fin ...