1、认识vuex
vuex是适用于在Vue项目开发时使用的状态管理工具。试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。为此,Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——vuex。在具有vuex的Vue项目中,我们只需要把这些值定义在vuex中即可,以便在整个Vue项目的组件中使用。
2、vuex安装
npm install vuex@next --save
3、使用
3.1、初始化store文件夹下的index.js文件
import { createStore } from "vuex";
//创建一个新的store实例
const store = createStore({
state: {
//存放的键值对就是要管理的状态
name: "hello vuex"
},
mutations: {},
actions: {},
modules: {},
});
export default store;
3.2、挂载到Vue实例中
让所有的Vue组件都可以使用这个store对象,来到main.js文件,导入store对象,并且放在Vue实例中,这样,在其他Vue组件中,我们就可以通过$store的方式,获取到这个store对象了。
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
3.3、在组件中使用vuex
例如在App.vue中,我们要将state中定义的name拿来在h1标签中显示:
<template>
<div id="app">
name:<h1>{{$store.state.name}}</h1>
</div>
<router-view/>
</template>
或者在组件方法中使用:
<template>
<div id="nav">
<button @click="name()">按钮</button>
</div>
</template>
<script>
import {useStore} from 'vuex';
export default {
setup() {
const store = useStore();
const name = ()=>{
console.log(store.state.name);
};
return {
name
}
},
}
</script>
我们对使用步骤,做一个简单的小节:
- 提取出一个公共的store对象,用于保存在多个组件中共享的状态。
- 将store对象放置在Vue实例中,这样可以保证在所有的组件中都可以使用到。
- 在其他组件中使用store对象中保存的状态即可。
4、Vuex核心概念
Vuex有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
4.1、State单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?
我们来看一个生活中的例子。在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
这个和我们在应用开发中比较类似。如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。所以Vuex也使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
4.2、Getters基本使用
可以对state的成员加工后传递给外界:
import { createStore } from "vuex";
//创建一个新的store实例
const store = createStore({
state: {
student:[
{id:10, name:'zhangsan', age:18},
{id:11, name:'lisi', age: 22},
{id:12, name:'wangwu', age: 23},
{id:13, name: 'zhaoliu', age:24}]
},
mutations: {},
actions: {},
modules: {},
});
export default store;
获取学生年龄大于20的个数。我们可以在Store中定义getters
getters: {
greaterAgeCount(state) {
return state.students.filter(s => s.age > 20).length;
}
}
在组件中调用:
$store.getters.greaterAgeCount
4.2.1、Getters作为参数和传递参数
Getters中的方法有两个默认参数:
-
- state:当前vuex对象中的状态对象。
-
- getters:当前getters对象,用于将getters下其他getter拿来用。
如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写
getters: {
greaterAgeStus(state) {
return state.students.filter(s => s.age > 20);
},
greaterAgeCount(state, getters){
return getters.greaterAgeStus.length
}
}
4.2.2、Mutation状态更新
mutations是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。
mutation的定义方式:
import { createStore } from "vuex";
//创建一个新的store实例
const store = createStore({
state: {
count: 1
},
getters:{},
mutations: {
increment(state){
state.count ++;
}
},
actions: {},
modules: {},
});
export default store;
通过 mutation 更新:
<template>
<div id="nav">
<button @click="increment">增加</button>{{$store.state.count}}
</div>
</template>
<script>
import {useStore} from 'vuex';
export default {
setup() {
const store = useStore();
const increment = ()=>{
store.commit('increment');
};
return {
increment
}
},
}
</script>
4.2.3、Mutation传递参数
在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数,参数被称为是mutation的载荷(Payload)。
Mutation中的代码:
mutations:{
increment(state, n){
console.log(state.count + n);
state.count += n;
}
}
但是如果参数不是一个呢? 这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象。
mutations:{
increment(state, payload){
state.count += payload.count;
}
}
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const increment = () => {
store.commit('increment', {'count': 3})
};
return {
increment
};
}
};
</script>
4.2.4、Mutation提交风格
上面的通过commit进行提交是一种普通的方式。Vue还提供了另外一种风格, 它是一个包含type属性的对象。
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const increment = () => {
store.commit({
type: 'increment',
count: 20
})
};
return {
increment
};
}
};
</script>
Mutation中的处理方式是将整个commit的对象作为payload使用, 所以代码没有改变, 依然如下:
mutations:{
increment(state, payload){
state.count += payload.count;
}
}
4.3、Action
4.3.1、Action的基本定义
我们强调, 不要再Mutation中进行异步操作,但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢? Action类似于Mutation, 但是是用来代替Mutation进行异步操作的。
Action的基本使用代码如下:
const store = createStore({
state: {
count: 0
},
mutations:{
increment(state){
state.count += 1;
}
},
actions:{
increment(context){
context.commit('increment');
}
}
})
context是什么?
- context是和store对象具有相同方法和属性的对象。
- 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等。
- 但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说。
这样的代码是否多此一举呢?
- 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
- 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了。
4.3.2、Action的分发
在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch。
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const increment = () => {
store.dispatch('increment');
};
return {
increment
};
}
};
</script>
同样的, 也是支持传递payload
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const increment = () => {
store.dispatch('increment', {'cCount': 6});
};
return {
increment
};
}
};
</script>
const store = createStore({
state: {
count: 0
},
mutations:{
increment(state, playload){
state.count += playload.cCount;
}
},
actions:{
increment(context, playload){
setTimeout(()=>{
context.commit('increment', playload);
}, 1000);
}
}
})
4.3.3、Action返回的Promise
前面我们学习ES6语法的时候说过, Promise经常用于异步操作。
在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject。
我们来看下面的代码:
const store = createStore({
state: {
count: 0
},
mutations: {
increment(state, playload) {
state.count += playload.cCount;
}
},
actions: {
increment(context, playload) {
return new Promise((resolve) => {
setTimeout(() => {
context.commit('increment', playload);
resolve();
}, 1000);
})
}
}
})
<script>
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const increment = () => {
store.dispatch('increment', {'cCount': 6}).then(()=>{
console.log("完成了更新操作");
}
);
};
return {
increment
};
}
};
</script>
4.4、Module
4.4.1、认识Module
Module是模块的意思, 为什么在Vuex中我们要使用模块呢? Vue使用单一状态树,那么也意味着很多状态都会交给 Vuex来管理。当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等。
我们按照什么样的方式来组织模块呢?
import { createStore } from 'vuex'
const moduleA = {
state: {},
mutations: {},
actions: {},
getters: {}
}
const moduleB = {
state: {},
mutations: {},
actions: {},
getters: {}
}
//创建一个新的store实例
const store = createStore({
modules:{
a: moduleA,
b: moduleB
}
})
export default store;
4.4.2、Module局部状态
上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写。
- 我们在moduleA中添加state、mutations、getters。
- mutation和getters接收的第一个参数是局部状态对象。
import { createStore } from 'vuex'
const moduleA = {
state: {
count: 0
},
mutations: {
increment(state){
state.count += 1;
}
},
actions: {
increment(context){
setTimeout(() => {
context.commit('increment');
}, 1000);
}
},
getters: {
doubleCount(state){
return state.count * 2;
}
}
}
const moduleB = {
}
//创建一个新的store实例
const store = createStore({
state:{
gCount: 111
},
modules:{
a: moduleA,
b: moduleB
}
})
export default store;
<template>
<div id="app">
<h3>count:{{$store.state.a.count}}</h3>
<h3>doubleCount:{{$store.getters.doubleCount}}</h3>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { computed } from '@vue/runtime-core';
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const doubleCount = computed(()=>{
return store.getters.doubleCount;
});
const increment = () => {
store.dispatch('increment');
};
return {
increment,
doubleCount
};
}
};
</script>