什么是状态管理
在开发中,我们的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是状态管理。
在之前我们是如何管理自己的状态的?
- 在Vue开发中,我们使用组件化的开发方式
- 而在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为state
- 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View
- 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions
复杂的状态管理
JS开发的应用程序已经变得越来越复杂了:
- JS需要管理的状态越来越多,越来越复杂
- 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等
- 也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
我们是否可以通过组件数据的传递来完成呢?
- 对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态
但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?
Vuex的状态管理
管理不断变化的state本身是非常困难的:
状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化
- 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了什么样的变化,会变得非常难以控制和追踪
因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?
- 在这种模式下,我们的组件树构成了一个巨大的“视图View”
- 不管在树的那个位置,任何组件都能获取状态或者触发行为
- 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码便会变得更加结构化和易于维护、跟踪
这就是Vuex背后的基本思想,它借鉴了Flux,Redux、Elm:
Vuex的基本使用
首先,我们需要在项目的根目录下新建一个store文件夹,用来存放vuex相关的文件。
然后新建一个index文件,用于使用vuex。
import { createStore } from "vuex";
const store = createStore({
state() {
return {
counter: 0,
};
},
mutations: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
},
});
export default store;
上面的代码就是index文件中的内容,我们从vuex中引入createSotre函数,然后调用这个函数并将返回值赋给store,最后导出store。
crateSotre函数中我们传入一个对象,对象中包含state和mutations,state就是存放数据的地方,mutations是操作数据的地方,因为案例没有使用异步函数,所以还不需要写actions。
<template>
<h2>{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</template>
<script>
export default {
name: "App",
data() {
return {};
},
methods: {
increment() {
this.$store.commit("increment");
},
decrement() {
this.$store.commit("decrement");
},
},
};
</script>
在vue中使用通过$.store.state来获取state中的参数,如果想要修改,不能直接通过$.store.state来对数据进行更改,而是通过this.$store.commit来调用store的mutations中定义的修改数据的方法,从而实现state数据的更改。
辅助函数mapState
我们使用state中的数据需要通过$store.state.xxxx
的形式,非常不方便,所以我们可以借助mapState函数简化操作。
<template>
<div>
<!-- <h2>Home:{{ counter }}</h2>
<h2>Home:{{ name }}</h2>
<h2>Home:{{ age }}</h2>
<h2>Home:{{ sno }}</h2> -->
<h2>Home:{{ sCounter }}</h2>
<h2>Home:{{ sName }}</h2>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
sCounter() {
return this.$store.state.counter;
},
sName() {
return this.$store.state.name;
},
// ...mapState(["counter", "name", "age", "sno"]),
...mapState({
sCounter: (state) => state.counter,
sName: (state) => state.name,
}),
},
// computed: mapState(),
};
</script>
mapState有两种使用方式,一种是数组使用,就是传入一个数组给mapState函数,数组项要和state中值相等,那么就可以拿到该值。
另一种方式是通过对象,对象中key值是我们自定义的key值,value值通过state=>state.xxx获取。
两种方式都需要解构。
getters的基本使用
某些属性我们可能需要经过变化后来使用,这个时候可以使用getters:
import { createStore } from "vuex";
const store = createStore({
state() {
return {
books: [
{ name: "深入Vuejs", price: 200, count: 3 },
{ name: "深入Webpack", price: 140, count: 5 },
{ name: "深入React", price: 280, count: 3 },
{ name: "深入Node", price: 180, count: 6 },
],
discount: 0.5,
};
},
getters: {
totalPrice(state, getters) {
return (
state.books.reduce((acc, cur) => {
return acc + cur.count * cur.price;
}, 0) * getters.currentDiscount
);
},
currentDiscount(state) {
return state.discount * 0.9;
},
},
});
export default store;
我们在store中创建了getters,在getters中创建了totalPrice方法和currentDiscount方法,getters中的方法可以接受两个参数,一个是state,还有一个是getters,getters用于在方法内部调用其他方法。
<template>
<div>
<h2>总价值:{{ $store.getters.totalPrice }}</h2>
</div>
</template>
使用和state差不多,直接从$store上取值。
<template>
<div>
<h2>总价值:{{ $store.getters.totalPrice(2) }}</h2>
</div>
</template>
totalPrice(state, getters) {
return function (n) {
console.log(n); // 2
return (
state.books.reduce((acc, cur) => {
return acc + cur.count * cur.price;
}, 0) * getters.currentDiscount
);
};
},
getters的参数
getters有三个参数,分别是:
- state:当前的state对象
- getters:当前的getters对象
- rootState:当前为子模块时,可以通过rootState获取父模块的state值
- rootGetters:当前为子模块时,可以通过rootGetters获取父模块的getters
我们可以给getter传递参数,在getter内层可以通过形参接收。
辅助函数mapGetters
作用和mapState一样,都是为了简化从store中取值的操作。
<template>
<div>
<div>{{ nameInfo }}</div>
<div>{{ ageInfo }}</div>
<div>{{ snoInfo }}</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
// ...mapGetters(["nameInfo", "ageInfo", "snoInfo"]),
...mapGetters({
nameInfo: "nameInfo",
ageInfo: "ageInfo",
snoInfo: "snoInfo",
}),
},
};
</script>
数组取值方式和mapState一样,而对象取值则是直接赋值为其在store中的key值即可。
mutations使用细节
<template>
<div>
<h2>当前计数:{{ $store.state.counter }}</h2>
<hr />
<button @click="$store.commit('increment')">+1</button>
<button @click="$store.commit('decrement')">-1</button>
<button @click="$store.commit('incrementN', 10)">10后</button>
<button @click="addTen">+10</button>
<button @click="$store.commit('incrementN', 100)">+100后</button>
</div>
</template>
<script>
export default {
methods: {
addTen() {
this.$store.commit("incrementN", 10);
},
},
};
</script>
在使用mutations的过程中,我们可能会传递一些数据给mutatiosn,可以直接在commit时传递。
mutations: {
increment(state) {
state.counter++;
},
incrementN(state, payload) {
state.counter += payload;
},
decrement(state) {
state.counter--;
},
},
incrementN在前面被commit时传递了额外的参数,我们可以通过payload获取(payload是形参不是实参,名字可以自定义),如果传递多个,可以通过对象或数组传递,也可以一个个传递,那么mutations中的方法接收时想要获取传递的参数,也要定义一相同数量的形参来接收。
mapMutations辅助函数
作用和其他两个一样,唯一的区别在于这个不写在computed中,而是写在methods中
<template>
<div>
<h2>当前计数:{{ $store.state.counter }}</h2>
<hr />
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="incrementN(10)">+10</button>
<button @click="addTen">+10</button>
<button @click="incrementN(100)">+100</button>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
methods: {
addTen() {
this.incrementN(10);
},
// ...mapMutations(["increment", "decrement", "incrementN"]),
...mapMutations({
increment: "increment",
decrement: "decrement",
incrementN: "incrementN",
}),
},
};
</script>
也同样有数组和对象两种使用方式,对象使用时,key是我们自己赋值的,value直接写和matations中一样的就可以自动匹配上。
actions的基本使用
action类似于mutation,不同在于:
- Action提交的是mutation,而不是直接变更状态
- Action可以包含任意异步操作 ```vue
![image.png](https://cdn.nlark.com/yuque/0/2022/png/422931/1645876403246-3cfcfcba-e67f-405a-88be-cd057b3f93a3.png#clientId=u75371319-8614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=174&id=u353a2d77&margin=%5Bobject%20Object%5D&name=image.png&originHeight=174&originWidth=755&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27019&status=done&style=none&taskId=ua3fdddd8-8384-4dc5-8767-e2374c87d3f&title=&width=755)
打印actions的context属性,可以看到在这里可以访问很多属性,比如通过commit可以调用mutations中的方法,也可以通过dispatch调用其他的actions方法,也可以使用getters获取getters中的方法。 1. commit:用来调用mutations中的方法 2. dispatch:用来调用其他的actions方法 3. getters:用来获取getters中的方法 4. rootState:当前为子模块时,可以通过rootState获取父模块的state值 5. rootGetters:当前为子模块时,可以通过rootGetters获取父模块的getters 6. state:用来获取当前的state值 ## mapActions辅助函数 和其他的辅助函数用法一致,也支持数组和对象两种方法,如果想要使用别名就需要使用对象方法。 ```vue
当前计数:{{ $store.state.counter }}
![image.png](https://cdn.nlark.com/yuque/0/2022/png/422931/1645883131296-2ac82d35-1da1-4805-a436-a8dc99e1f991.png#clientId=u75371319-8614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=158&id=u86d035c8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=158&originWidth=387&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8096&status=done&style=none&taskId=ub0538adb-840f-438e-9601-9f42c77acd8&title=&width=387)
![image.png](https://cdn.nlark.com/yuque/0/2022/png/422931/1645883175152-896445d6-012d-47a1-afeb-e2dbcb0e3d13.png#clientId=u75371319-8614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=348&id=u5b6706d4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=423&originWidth=395&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29964&status=done&style=none&taskId=u4ad2ba5a-67a6-4086-bb3f-ce0f6750f7b&title=&width=325)![image.png](https://cdn.nlark.com/yuque/0/2022/png/422931/1645883181830-d9189157-8d4a-419d-8218-0c844c6264f7.png#clientId=u75371319-8614-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=348&id=u2b222a43&margin=%5Bobject%20Object%5D&name=image.png&originHeight=425&originWidth=391&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29610&status=done&style=none&taskId=u6675aa8f-76f4-4191-a028-562badb305e&title=&width=320)
单个模块其实可以看做是一个单独的store ```javascript import { createStore } from "vuex"; import home from "../stroe/modules/home"; import user from "../stroe/modules/user"; const store = createStore({ state() { return { rootCounter: 0, }; }, mutations: { increment(state) { state.rootCounter++; }, }, modules: { home, user, }, }); export default store; ``` 在store文件夹下的index文件中我们将各个模块的文件导入进来,然后在modules中注册他们 ```vue
{{ $store.state.rootCounter }}
{{ $store.state.home.homeCounter }}
{{ $store.state.user.userCounter }}
{{ homeCounter }}
{{ doubleHomeCounter }}
{{ homeCounter }}
{{ doubleHomeCounter }}
``
这种写法依赖于
createNamespacedHelpers函数,传递模块名称给它,那么它就能获取该模块的
mapState, mapGetters, mapMutations, mapActions`方法。