参考文档:
Vuex Guide
1.基础知识
-
vuex用来帮助我们管理共享状态
-
状态是一种短暂存储在内存堆栈的数据,一旦刷新网页,如果不做维护的话,状态将全部消失
-
使用状态的优点是,能够实时响应变化,源状态改变,其他使用该状态的组件中的引用状态将同步变化
-
每一个 Vuex 应用的核心就是 store(仓库),它是一个包含着你的应用中大部分的状态 (state)的容器
-
Vuex 和单纯的全局对象有以下两点不同:Vuex 的状态存储是响应式的;改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation
-
store最核心的两个概念是state 和mutations,前者定义状态,后者改变状态,通过 store.state 来获取状态对象,并通过 store.commit 方法触发状态变更
-
store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。
-
存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则:状态对象必须是纯粹的对象 (含有零个或多个的 key/value 对)
-
Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态
-
vuex中的方法或者属性映射到某个组件上中:同名映射传字符串数组,不同名映射传对象
2. 使用Vuex(单状态树)
- 项目中导入依赖
npm install vuex@3 --save
,注意这个版本需要与Vue的版本对应 - 创建store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
//将 store 实例作为插件安装
Vue.use(Vuex);
//对外暴露store类的一个实例
export default new Vuex.Store({
state: {
userInfo: {
userName: ''
}
},
mutations: {
userInfo(state, userInfo) {
state.userInfo = userInfo
}
},
})
- main.js中注册
import store from './store';
new Vue({
store,
render: h => h(App)
}).$mount('#app')
3.State(声明状态)
- 在store中声明状态
对象{key:value},对象数组[],字符串,数字 - 在 Vue 组件中获得 Vuex 状态
假设vuex的state中声明了一个count状态,在组件中获取这个count状态:
computed: {
count () {
return this.$store.state.count
}
}
或者使用mapState 辅助函数获取这个count状态(对于一个组件需要获取多个状态的情况特别方便):
映射出来起个别名,传键值对,别名是键,仓库中的名是字符串值
computed: {
localComputed () {
/* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
不起别名的话,直接传字符串
computed: {
localComputed () {
/* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState([
// ...
])
}
4.Mutation(同步修改)
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。mutation 必须是同步函数,里面不能出现回调函数,就是说方法体执行完成后,状态就会直接发生变化,配合action理解比较好这个。
假设,mutation中存在两个异步操作,并且假设它们都对同一个状态进行直接修改,但是由于它们是互为异步操作,我们不能知道它们中谁会先执行完成,所以对于状态的修改结果我们无法知道,为了方便跟踪状态变化,我们不允许在mutation中有异步回调
每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler),其中回调函数会接受 state 作为第一个参数
mutation 事件类型说白了就是方法名,回调函数就是方法体,state 是方法的第一个参数
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)
mutations: {
increment (state, n) {
state.count += n
}
}
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
通过commit的形式唤醒一个 mutation 处理函数
//无参
store.commit('increment')
//普通带参
store.commit('increment',10)
//参数对象化
store.commit('increment', {
amount: 10
})
对象风格的提交方式:
处理函数:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit({
type: 'increment',
amount: 10
})
在组件中提交 Mutation:
- 使用 this.$store.commit(‘xxx’) 提交 mutation
- 使用mapMutations 辅助函数将组件中的 methods 映射为 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')`
})
}
5.Action(异步修改)
Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作
actions: {
increment (context) {
context.commit('increment')
}
}
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。
实践中,用参数解构来简化代码(特别是我们需要调用 commit 很多次的时候):
actions: {
increment ({
commit }) {
commit('increment')
}
}
Action 通过 store.dispatch 方法触发:store.dispatch('increment')
不同于mutation 必须同步执行的限制,我们可以在 action 内部执行异步操作:
Actions 支持同样的载荷方式和对象方式进行分发
在组件中分发 Action:
1.使用 this.$store.dispatch(‘xxx’) 分发 action
2.使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用
import {
mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
6.Getter(派生状态)
从 store 中的 state 中派生出一些状态
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
Getter 接受 state 作为其第一个参数
Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值
Getter 也可以接受其他 getter 作为第二个参数
在任何组件中使用它:this.$store.getters.属性名
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
7.总结
-
vuex共享状态管理,在我看来一个最大的优点是一个组件的数据变化了,可以同步引起另一个组件数据的变化,这对于SPA是非常好的特性
-
仓库和组件间存在映射关系:state和getters都是映射到组件的计算属性中,而actions和mutations都是映射到组件的方法中
-
通过 …mapXXX(数组/对象) 来与组件的原有方法或计算属性进行混合挂载,然后直接通过this来访问,this指的是当前组件实例对象
-
对于使用…mapXXX有两种情况:
要是想直接用仓库中的名字来挂载到组件实例上,我们传递一个字符串数组,字符串是名字
要是想给仓库中的名字取一个别名,然后再挂载到组件实例上,我们传键值对类型的对象,键是别名,值是仓库中的名 -
也可以不做映射,直接使用已经注入挂载到组件的this.$store 对象来访问仓库中的属性和方法,this指的是当前组件实例对象
具体是:
this.$store.state.属性名
this.$store.commit(‘mutation类型名’,载荷对象)
this.$store.getters.属性名/方法名
this.$store.dispatch(‘action类型名’,载荷对象)
- state和mutation核心概念,而getter和action分别是它们的衍生概念
8.感悟
我觉得程序员是一个需要经常学习的群体,应当追随“时尚”,如何学习一门新技术呢?我的经验是按照如下步骤进行认识迭代:
- 刚刚接触新技术的话,去看官方文档你可能看不懂,如果可以看懂,最好就从官方文档中学习总结和实践,看不懂可以借助一些视频网站跟随先行者进行学习,强调要边学边练
- 有了一定的技术基础的话,可以在做一个项目练手,在这中间,最好把在使用新技术的过程中有的理解以及遇到的问题,以及如何解决这个问题的步骤整理记录下来,可以分享出来,我的感悟是,当你试图学习和理解一个东西时,你可以试图去想象你是一个老师,你会如何去教那些不会这个技术的人呢,你会怎样组织你的语言去理解和描述它呢,当你试图用你的知识做输出时,你很快就会发现自己的问题,善用搜索引擎是解决这中间产生问题的好办法,这就是所谓的教学相长吧!
- 做个一个项目后,你会发现许多问题,这时候,官方文档又一次出现了,第一次看不懂,现在有了理解后,再来拜访一下,学习总结一下,你会有了更好的理解和系统化的知识框架了。
- 如哲学所说:人的认识发展是一个不断随实践的深入而螺旋上升的过程,我们现在只需要在看文档和做项目间循环往复进行认识迭代即可