Vuex 使用详解
说明
Vuex 是一个专门为 Vue 应用程序开发的状态管理模式,适合开发大型单页应用, 它能够更好的在组件外部管理状态
重要文件版本
- “vue”: “^2.5.2”,
- “vuex”: “^3.0.0”
特点
每一个 Vuex 应用的和兴就是 store(仓库)。store 基本上就是一个容器,它包含着应用中大部分的状态(state) Vuex 和单纯的全局对象有以下不同
- Vuex的状态储存是响应式的,当 Vue 组件从 store 中读取状态时,若store中的状态发生变化,那么相应的组件也会得到高效更新
- 不能直接改变 store 中的状态,改变 store 中状态的唯一途径就是显式的提交(commit)mutation 这样可以更方便的跟踪状态的变化
1. 安装及引入
1. 下载
npm install --save vuex
2. 安装
// 配置 store
import Vue from 'vue';
import Vuex from 'vuex';
// 通过 Vue.use() 安装 Vuex
Vue.use(Vuex);
const store = new Vuex.Store({
state: {},
});
export default store;
// main.js 中引入 store
import store from './store/index';
new Vue({
el: '#app',
store, // 将 store 实例从根组件中‘注入’到每一个子组件中,子组件通过 `this.$store` 调用
template: '<app/>',
components: { App }
});
2. API 详解
一个完整的 store 配置
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {
home: {
state: {},
getters: {},
mutations: {},
actions: {},
}
},
strict: process.env.NODE_ENV !== 'production'
});
1. state
Vuex 中使用单一状态树,包含了全部应用层级状态,每个应用将仅仅包含一个 store 实例
在 Vue 组件中获得 Vuex 状态
// 根组件中注入
import store from './store.js';
const vm = new Vue({
el: '#app',
store, // store 实例注入
})
// Vue 组件中使用
const Child = {
template: `<div>this is {{count}}</div>`,
computed: {
count() {
return this.$store.state.count;
}
}
}
2. mapState 辅助函数
为了解决 组件在获取多个状态的时候,频繁声明为计算属性有些重复和冗余,可以使用 mapState 辅助函数帮助生成计算属性
import { mapState } from 'vuex';
import HomeBasic from '../components/Home/HomeBasic';
export default {
name: 'Home',
data () {
return {};
},
components: { HomeBasic },
computed: {
step() { // 普通计算属性
return 3;
},
...mapState({ // 从 state 中的到的计算属性
count: state => state.global,// 可以是一个函数
count: 'global', // 可以是一个字符串,等同于 ‘state => state.global’
count(state) { // 可以计算
return state.global - this.step;
}
})
},
};
3. getter
state 中一般存放的都是比较原始的数据,我们的应用中使用的可能会是这些原始的数据经过计算后的数据,这时候我们可以在 组件的计算属性中操作
computed: {
...mapState({
count(state) {
return state.global * 100;
}
})
},
如果有多个组件都需要使用这种计算后的属性,这个时候可以使用 getter ,一种类似 Vue 组件中计算属性的方式,同样 getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
// 定义 getter
const store = new Vuex.Store({
state: {
global: 100,
step: 3
},
getters: {
count(state) { // 第一个参数是 state
return state.global * 200;
},
changeCount(state, getters) { // 可以传入两个参数 第二个参数是当前的 getter
return getters.count - state.step;
},
addCount: (state, getters) => (num) => { // 值可以是一个函数,用于传递参数
return getters.count - state.step + num;
}
},
});
// 组件中使用
computed: {
count() {
return this.$store.getters.count;
},
count2() {
return this.$store.getters.changeCount;
},
count3() {
return this.$store.getters.addCount(10); // 传参
},
...mapState({
})
},
4. mapGetters 辅助函数
与 mapState() 类似 mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
computed: {
...mapGetters([ // 参数为数组时,可以将同名的属性映射
'count'
]),
...mapGetters({ // 参数为对象时可以设置映射的名称
count2: 'changeCount'
})
},
5. mutation
以上 state、getter 都是用来获取 store 中状态的,而更改 store 中状态只能通过提交 mutation ,每个 mutation 都由一个事件类型(type)和一个事件处理函数(handler),事件处理函数接受 state 作为参数,只有在这里才可以进行状态更改
提交 mutation 需要用到 store 的
store.commit()
方法注意: mutation 函数必须是同步的
// 创建 mutation
const ADD_COUNT = 'ADD_COUNT'; // 用常量代替事件类型,使得代码更清晰
const store = new Vuex.Store({
state: {
global: 100,
step: 3
},
mutations: {
[ADD_COUNT] (state, num) { // 第一个参数为 state 用于变更状态
state.global += num;
},
[ADD_COUNT] (state, payload) { // 第二个参数为提交的参数,参数类型视提交方式而定
state.global += payload.num;
},
}
});
// 组件中提交
methods: {
handleClick() {
this.$store.commit('ADD_COUNT', 30); // 普通提交 第二个参数为参数
this.$store.commit('ADD_COUNT', { num: 30 }); // 可以将参数包装到一个对象中提交
this.$store.commit({ // 可以直接提交一个对象,对象中 type 属性对应 事件类型,其他属性, 成为事件处理函数的第二个参数对象中的属性
type: 'ADD_COUNT',
num: 30
});
},
},
6. mapMutations 辅助函数
同 mapState 和 mapGetters 一样,mapMutations 可以节约代码,使代码更加简洁,他将会在组件的 methods 属性中完成映射
methods: {
handleClick() {
this.addCount({ num: 40 });
this.ADD_COUNT({ num: 60 });
},
...mapMutations({
addCount: 'ADD_COUNT' // 将 this.addCount({}) 映射为 this.$store.commit('ADD_COUNT', {})
}),
...mapMutations([ // 参数为数组表示函数名不变
'ADD_COUNT'
])
},
7. action
action 类似于 mutation, 不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
// 创建 actions
const store = new Vuex.Store({
state: {
global: 100,
step: 3
},
mutations: {
[ADD_COUNT] (state, payload) {
state.global += payload.num;
},
},
actions: {
changeCount(context, payload) { // 第一个参数为 store 实例类似的对象
context.commit({
type: ADD_COUNT,
...payload
});
},
changeCount({ commit }, payload) { // 一般使用解构赋值
commit({
type: ADD_COUNT,
num: payload.num
});
},
changeCount({ commit }) { // action 中可以尽心异步操作,并且可以提交多次 mutation
request('/api').then(res => {
commit({
type: ADD_COUNT,
num: res.data.num
});
}).catch(() => {
commit({
type: ERROR
});
});
}
}
});
// Vue 组件中使用
methods: {
handleClick() {
this.$store.dispatch('changeCount', { num: 1 });
this.$store.dispatch({ // 注意如果使用这种方式,changeCount 接受的 payload 中是包含 type: 'changeCount' 字段的 commit 的时候要去除
type: 'changeCount',
num: 30
});
},
},
8. mapActions 辅助函数
与 mapMutations 类似,用于将 actions 映射到组件中
methods: {
handleClick() {
this.changeCount({ num: 3 });
},
...mapActions({
changeCount: 'changeCount'
})
...mapActions([
'changeCount'
])
},
actions 可以组合使用,也就是在一个 action 中调用另外一个 action
9. module
解决使用单一状态树,所有状态集中,使得对象过大的问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 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,接收的第一个参数是模块的局部状态对象。
- 对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
- 对于模块内部的 getter,根节点状态会作为第三个参数暴露出来
10. 模块化使用方式
在辅助函数中使用 module 中数据
export default {
computed: {
...mapState({
// 要使用 模块中的 state 需要 通过 state.[模块名].[模块数据] 引用
moduleAData: state => state.moduleA.moduleAData
}),
...mapGetters({
// 要使用 模块中的 getter 根使用全局的 getter 一致,因为模块内的 getter 是注册在全局命名空间的
moduleAGetterData: 'moduleAGetterData'
})
},
methods: {
// mutation、action 与 getter 一样,模块内的 mutation、action 也是注册在全局命名空间的
...mapMutations({
changeTestList: 'changeTestList'
}),
...mapActions([
changeTestList: 'changeTestList'
])
},
components: {
SideBarMenu, BasicHeader
}
};
给模块添加命名空间,以提高封装性
模块中添加
namespaced: true
可以使其成为命名空间模块,模块被注册后,模块内所有 getter action mutation 都会 自动根据 模块注册路径调整命名 (如下)
// 定义模块
const moduleA = {
namespaced: true, // 添加命名空间
state: {
test: 'abc'
},
getters: {
upperTest(state) {
return state.test.toUpperCase();
}
},
mutations: {
changeTest(state, paylaod) {
state.test = payload.test;
}
}
}
组件中使用
export default {
computed: {
...mapState({
test: state => state.moduleA.test // 模块中的 state 本来就是有层级的所以这里不变
})
...mapGetters({
upperTest: 'moduleA/upperTest' // 这里的引用要加上模块名
})
},
methods: {
...mapMutations({
changeTest: 'moduleA/changeTest' // action 与 mutation 与 getter 的使用一致
})
},
}
组件中使用的简易写法 给 mapState mapGetters mapMutations mapActions 的第一个参数传入模块的空间名称,这样所有绑定都会自动将该模块作为上下文。
export default {
computed: {
...mapState('moduleA', ['test']) //传入第一个参数表示绑定的上下文
...mapGetters('moduleA', {
upperTest: 'upperTest'
})
},
methods: {
...mapMutations('moduleA', {
changeTest: 'changeTest'
})
},
}
11. 严格模式
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production'
})
对比 redux
- Vuex 与 Redux 都使用了单一组件树,状态都存放在 state 中
- 如果某个状态的使用涉及到固定的计算处理,那么 Vuex 中可以通过设置 getter 来提取出公共的状态变化方法,而 Redux 中只能在各个组件中处理或者提取出一个函数
- Vuex 中状态更改只能通过提交(commit)mutation ,而且不能是异步的, Redux 中 reducer 也包括这部分功能;reducer 会接收含有事件类型的对象(type属性)然后根据 type 的类型返回新的 state ,Vuex 中的 mutation 会根据不同的事件类型,对传入的 state 进行操作。
- Vuex 中如果有异步操作需要更改状态的,要使用 action,action 用来在异步操作后 通过 commit 提交 mutations; 在 Redux 中 action 的功能与其一致,提交更改用的是 dispatch 方法(Vuex 中也有 dispatch 方法,但是是用来触发 action 的)。
- Vuex 中分割模块用的是 module 属性,这个需求在 Redux 中是通过 创建 reducer 是,分模块引入不同的 初始 state 完成的。
- Vuex 中为了方便在组件中使用 状态或者状态变更函数,引入了 一些辅助函数 mapState、mapGetters、mapMutations、mapActions;Redux 为配合 React 需要引入 React-Redux 的 connect 方法,将状态与状态处理函数注入到组件中,实现了同样功能