Vuex 应用的核心就是 store,含着大部分的状态 (state)
Vuex 和单纯的全局对象有以下两点不同:
-
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
-
不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation
一、stroe,Vuex 使用的单一状态树,用一个对象就包含了全部的应用层级状态,作为一个“唯一数据源",每个应用将仅仅包含一个 store 实例
1、新建文件夹src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
export default store;
2、main.js
import Vue from 'vue'
import store from './store/index'
new Vue({
store, //把 store 的实例注入所有的子组件
render: h => h(App)
}).$mount('#app')
3、在页面中获取state数据
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性 中返回某个状态,也可以直接在页面中使用this.$store.state来获取定义的数据。
<template>
<div>
<div>{{this.$store.state.count}}</div>
<div>{{count1}}</div>
</div>
</template>
<script>
export default {
data () {
return {}
},
computed: {
count1: function () {
return this.$store.state.count
}
}
}
</script>
4、mapState辅助函数
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性
<template>
<div>
<div>{{count}}</div>
<div>{{allCount}}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
num: 5
}
},
computed: mapState({
count: state => state.count,
allCount: function (state) { // 为了能够使用 `this` 获取局部状态num,必须使用常规函数
return state.count + this.num
}
})
}
</script>
上面的方法不方便的是不能与局部计算属性混用,mapState 函数返回的是一个对象,所以我们可以使用展开运算符
<template>
<div>
<div>{{count}}</div>
<div>{{allCount}}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
num: 5
}
},
computed: {
...mapState({
count: state => state.count,
allCount: function (state) {
return state.count + this.num
}
})
}
}
</script>
而且当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组
<template>
<div>
<div>{{count}}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapState(['count'])
}
}
</script>
二、getter
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数,也可以接受其他 getter 作为第二个参数
1、store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
twoCount: state => state * 2,
todoLen: function(state,getters){
return getters.twoCount
}
}
})
export default store;
2、页面中,Getter 会暴露为 store.getters 对象,在页面中访问this.$store.getters,也可以在计算属性种返回:
<template>
<div>
<div>{{this.$store.getters.twoCount}}</div>
<div>{{getterTodoLen}}</div>
</div>
</template>
<script>
export default {
data () {
return {}
},
computed: {
getterTodoLen: function() {
return this.$store.getters.todoLen
}
}
}
</script>
3、mapGetters辅助函数
mapGetters 辅助函数是将 store 中的 getters 映射到局部计算属性:
使用对象展开运算符将 getter 混入 computed 对象中
<template>
<div>
<div>{{twoCount}}</div>
<div>{{todoLen}}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters(['twoCount', 'todoLen'])
}
}
</script>
如果想将一个 getter 属性另取一个名字,可以使用对象形式
把 `this.XXX` 映射为 `this.$store.getters.XXX`
<template>
<div>
<div>{{twoCountName}}</div>
<div>{{todoLenName}}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters({
twoCountName: 'twoCount',
todoLenName: 'todoLen'
})
}
}
</script>
三、Mutation, 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件,它会接受 state 作为第一个参数,也可以提交额外的参数,即 mutation 的 载荷(payload)
1、store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
twoCount: state => state * 2,
todoLen: function(state,getters){
return getters.twoCount
}
},
mutations: {
addNum (state) {
state.count = state.count + 2;
},
reduce (state, n) {
state.count = state.count - n;
}
}
})
export default store;
2、在页面中,在组件中使用this.$store.commit('xxx') 提交 mutation
<template>
<div>
<div>{{twoCount}}</div>
<div>{{todoLen}}</div>
<button @click="add">增加</button><button @click="reduce">减少</button>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters(['twoCount','todoLen'])
},
methods: {
add: function() {
// 触发mutations里addNum事件,改变count
this.$store.commit('addNum')
},
reduce: function() {
// 触发mutations里reduceNum事件,改变count
let n = 3;
this.$store.commit('reduceNum', n); // n为额外的参数,即载荷
}
}
}
</script>
mutation 的 载荷(payload)也可以是一个对象,包含多个字段
reduce: function() {
// 触发mutations里reduceNum事件,改变count
let params = {
num: 3,
age: 20
};
this.$store.commit('reduceNum', params)
}
reduceNum (state, params) {
state.count = state.count - params.num;
}
Mutation的注意事项:
-
最好提前在你的 store 中初始化好所有所需属性。
-
当需要在对象上添加新属性时,你应该
使用Vue.set(obj, 'newProp', 123) - 以新对象替换老对象。例如,利用对象展开运算符 我们可以这样写:
state.obj = { ...state.obj, newProp: 123 }
3、使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
新建store/mutation-types.js
export const SET_COUNT = 'SET_COUNT'
store/index.js,使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
import Vue from 'vue'
import Vuex from 'vuex'
import SET_COUNT from './mutation_type'
Vue.use(vuex)
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
twoCount: state => state * 2,
todoLen: function(state,getters){
return getters.twoCount
}
},
mutations: {
addNum (state) {
state.count = state.count + 2;
},
reduceNum (state, params) {
state.count = state.count - params.num;
},
[SET_COUNT] (state) {
return state.count
}
}
})
在页面中
add: function() {
this.$store.commit('SET_COUNT')
},
如果你不喜欢,你完全可以不这样做。
一条重要的原则就是mutation 必须是同步函数。
4、mapMutations辅助函数
在组件中除了使用 this.$store.commit('xxx') 提交 mutation,也可以使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用
<template>
<div>
<div>{{twoCount}}</div>
<div>{{todoLen}}</div>
<button @click="add">增加</button><button @click="reduce">减少</button>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters(['twoCount','todoLen'])
},
methods: {
// 将 `this.addNum()` 映射为 `this.$store.commit('addNum')`
// 使用字符串数组,把mutations里的方法映射为本地组件方法
...mapMutations(['addNum', 'reduceNum']),
add (){
this.addNum()
},
reduce () {
let n = 3;
this.reduceNum(n);
}
}
}
</script>
也可以自定义修改mutaions里的方法在本地组件中的名字
methods: {
// 将 `this.addNum()` 映射为 `this.$store.commit('addNum')`
// 使用字符串数组,把mutations里的方法映射为本地组件方法
...mapMutations({
addN: 'addNum',
reduceN: 'reduceNum'
}),
add (){
this.addN()
},
reduce () {
let n = 3;
this.reduceN(n);
}
}
四、Action,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
官方建议我们不直接通过mutation改变state,而是先提交actions,在actions里提交mutaion,再去修改state
1、store/index.js
在actions里定义提交mutaions的函数
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
twoCount: state => state * 2,
todoLen: function(state,getters){
return getters.twoCount
}
},
actions: {
addNum(context){
context.commit('addNum')
},
reduceNum(context){
context.commit('reduceNum')
}
},
mutations: {
addNum (state) {
state.count = state.count + 2;
},
reduceNum (state, params) {
state.count = state.count - params.num;
}
}
})
export default store;
实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候)
actions: {
addNum({commit, state}){
commit('addNum')
},
reduceNum({commit}){
commit('reduceNum')
}
},
而且,可以在actions方法里面执行异步操作
actions: {
addNum({commit}){
setTimeout(()=>{
commit('addNum')
},1000)
},
reduceNum({commit}){
commit('reduceNum')
}
},
2、在页面中,通过store.dispatch('addNum')触发方法
<template>
<div>
<div>{{twoCount}}</div>
<div>{{todoLen}}</div>
<button @click="add">增加</button><button @click="reduce">减少</button>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters(['twoCount','todoLen'])
},
methods: {
add (){
this.$store.dispatch('addNum')
},
reduce () {
this.$store.dispatch('reduceNum')
}
}
}
</script>
Actions 支持同样的载荷方式和对象,即actions可以传第二个参数,即额外的参数
页面中
<template>
<div>
<div>{{twoCountName}}</div>
<div>{{todoLenName}}</div>
<button @click="add">增加</button><button @click="reduce">减少</button>
</div>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters({
twoCountName: 'twoCount',
todoLenName: 'todoLen'
})
},
methods: {
add (){
let n = 3;
this.$store.dispatch('addNum', n)
},
reduce () {
let params = {
num : 2
}
this.$store.dispatch('reduceNum', params)
}
}
}
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
twoCount: state => state * 2,
todoLen: function(state,getters){
return getters.twoCount
}
},
actions: {
addNum({commit}, n){
setTimeout(()=>{
commit('addNum', n)
},1000)
},
reduceNum({commit}, params){
commit('reduceNum', params)
}
},
mutations: {
addNum (state, n) {
state.count = state.count + n;
},
reduceNum (state, params) {
state.count = state.count - params.num;
}
}
})
export default store;
3、mapActions辅助函数
在组件中可以使用 this.$store.dispatch('xxx') 分发 action,也可以使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用,将actions方法映射为本地方法
import { mapGetters, mapMutations, mapActions } from 'vuex'
export default {
data () {
return {}
},
computed: {
...mapGetters({
twoCountName: 'twoCount',
todoLenName: 'todoLen'
})
},
methods: {
// 将 `this.addNum()` 映射为 `this.$store.dispatch('addNum')`
...mapActions(['addNum', 'reduceNum']),
add (){
let n = 3;
this.addNum(n) // 传参
},
reduce () {
let params = {
num : 2
}
this.reduceNum(params) // 传参
}
}
}
</script>
本文深入解析Vuex状态管理模式,涵盖store、getter、mutation及action核心概念,展示如何通过响应式状态更新优化Vue组件,提供实用代码示例。
7042

被折叠的 条评论
为什么被折叠?



