Vuex
# 一、认识Vuex
# 1、Vuex是做什么的?
官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- 它采用 集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理到底是什么?
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
- 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
- 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
- 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
- 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
- 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
# 2、管理什么状态?
但是,有什么状态是需要我们在多个组件间共享的呢?
- 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
- 比如用户的登录状态、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品等等。
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的(待会儿我们就可以看到代码了,莫着急)。
OK,从理论上理解了状态管理之后,让我们从实际的代码再来看看状态管理。
# 3、单界面的状态管理
- 我们知道,要在单个组件中进行状态管理是一件非常简单的事情
- 什么意思呢?我们来看下面的图片。
- 这图片中的三种东西,怎么理解呢?
- State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)
- View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)
- Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
- 写点代码,加深理解:
# 4、单界面状态管理的实现
<!-- App.vue -->
<template>
<div id="app">
<h2>{{message}}</h2>
<h2>{{counter}}</h2>
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data() {
return {
message:'我是App组件',
counter:0
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- 在这个案例中,我们有木有状态需要管理呢?没错,就是个数counter。
- counter需要某种方式被记录下来,也就是我们的State。
- counter目前的值需要被显示在界面中,也就是我们的View部分。
- 界面发生某些操作时(我们这里是用户的点击,也可以是用户的input),需要去更新状态,也就是我们的Actions 这不就是上面的流程图了吗?
# 5、多界面状态管理
- Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
- 多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
- 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)
- 也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个视图,但是也有一些
- 状态(状态a/状态b/状态c)属于多个试图共同想要维护的
- 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
- 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
- 没错,Vuex就是为我们提供这个大管家的工具。
- 全局单例模式(大管家)
- 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
- 之后,你们每个试图,按照我规定好的规定,进行访问和修改等操作。
- 这就是Vuex背后的基本思想。
- 如果互为父子组件的话,可以用props
- 新建组件
HelloVuex.vue
,HelloVuex.vue
和App.vue
代码如下
- 新建组件
<!-- HelloVuex.vue -->
<template>
<div>
<h2>{{counter}}</h2>
</div>
</template>
<script>
export default {
name:'HelloVuex',
props:{
counter:Number
}
}
</script>
<!-- App.vue -->
<template>
<div id="app">
<h2>{{message}}</h2>
<h2>{{counter}}</h2>
<button @click="counter++">+</button>
<button @click="counter--">-</button>
<HelloVuex :counter="counter"></HelloVuex>
</div>
</template>
<script>
import HelloVuex from './components/HelloVuex.vue'
export default {
name: 'App',
components: {
HelloVuex
},
data() {
return {
message:'我是App组件',
counter:0
}
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- 但是组件没有父子关系的话,就得使用 Vuex
# 6、Vuex状态管理图例
- 一起在来看一副官方给出的图片
# 二、 Vuex基本使用
// 安装vuex
npm install vuex --save
1
2
2
# 1、简单的案例
我们还是实现一下之前简单的计数器案例
首先,我们需要在某个地方存放我们的Vuex代码:
- 这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件
- 在index.js文件中写入如下代码:
import Vue from 'vue'
import Vuex from 'vuex'
// 1.安装插件
Vue.use(Vuex)
// 2.创建对象 new Vuex.Store
const store = new Vuex.Store({
// 这5个对象一般都是固定的
state: { //保存状态
counter: 1000
},
mutations: { // 方法 修改state唯一途径 同步操作
increment(state) { // 默认就有个state参数,不用通过this.state
state.counter++
},
decrement(state) {
state.counter--
}
},
actions: { // 如果有异步操作在这里写 比如网络请求
},
getters: {
},
modules: {
}
})
// 3.导出store对象
export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 2、挂载到Vue实例中
- 其次,我们让所有的Vue组件都可以使用这个store对象
- 来到main.js文件,导入store对象,并且放在new Vue中
- 这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
main.js 代码:
import Vue from 'vue'
import App from './App'
import store from './store'
Vue.config.productionTip = false
// 其他组件就能通过 $store 获取到store
new Vue({
el: '#app',
store,
render: h => h(App)
})
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3、使用Vuex的count
<template>
<div id="app">
<h2>-----App.vue内容-----</h2>
<!-- 通过this.$store.state.属性的方式来访问状态 -->
<h2>{{ $store.state.counter }}</h2>
<!-- 官方不建议这么改,因为工具devtools监听不到 -->
<!-- <button @click="$store.state.counter++">+</button>
<button @click="$store.state.counter--">-</button> -->
<button @click="addition">+</button>
<button @click="subtraction">-</button>
<hr />
<h2>-----HelloVuex内容-----</h2>
<HelloVuex></HelloVuex>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex.vue";
export default {
name: "App",
components: {
HelloVuex,
},
data() {
return {};
},
methods: {
// 在这里提交方法对应的mutation 的方法名
addition() {
// 通过this.$store.commit('mutation中方法')来修改状态
this.$store.commit("increment");
},
subtraction() {
this.$store.commit("decrement");
},
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- 好的,这就是使用Vuex最简单的方式了。
- 我们来对使用步骤,做一个简单的小节:
- 1.提取出一个公共的store对象,用于保存在多个组件中共享的状态
- 2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
- 3.在其他组件中使用store对象中保存的状态即可
- 通过this.$store.state.属性的方式来访问状态
- 通过this.$store.commit('mutation中方法')来修改状态
- 注意事项:
- 我们通过提交mutation的方式,而非直接改变store.state.count。
- 这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。
# 三、Vuex核心概念
- Vuex有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
- 我们对它进行一一介绍.
# 四、 state单一状态树
- Vuex提出使用单一状态树, 什么是单一状态树呢?
- 英文名称是Single Source of Truth,也可以翻译成单一数据源。
- 但是,它是什么呢?我们来看一个生活中的例子。
- OK,我用一个生活中的例子做一个简单的类比。
- 我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
- 这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
- 这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
- 这个和我们在应用开发中比较类似:
- 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
- 所以Vuex也使用了单一状态树来管理应用层级的全部状态。
- 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
# 五、Getters
# 1、Getters 基本使用
- 有时候,我们需要从store中获取一些state变异后的状态,比如下面的Store中:
- 获取学生年龄大于20的个数。
store
下 index.js
代码:
const store = new Vuex.Store({
// 这五个一般都是固定的,是对象
state: { //保存状态
counter: 1000,
students: [{
id: 110,
name: 'why',
age: 18
},
{
id: 111,
name: 'kobe',
age: 24
},
{
id: 112,
name: 'james',
age: 30
},
{
id: 113,
name: 'curry',
age: 10
}
],
},
// ...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 可以用computed计算属性,但是这样的话,代码不好复用,其他组件使用不方便
App.vue
代码 :
// App.vue
computed: {
// 用计算属性的话 代码不好复用
// 找出多于20岁的学生
more20stu() {
return this.$store.state.students.filter(s => s.age > 20)
}
},
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 我们可以在Store中定义getters
store
下 index.js
代码:
const store = new Vuex.Store({
// 这五个一般都是固定的,是对象
state: { //保存状态
counter: 1000,
students: [{
id: 110,
name: 'why',
age: 18
},
{
id: 111,
name: 'kobe',
age: 24
},
{
id: 112,
name: 'james',
age: 30
},
{
id: 113,
name: 'curry',
age: 10
}
],
},
getters: { // 可以认为是 store 的计算属性
// getters里面的方法 也会有state参数
powerCounter(state) {
return state.counter * state.counter
},
// 找出多于20岁的学生
// filter(回调函数(当前元素的值))
more20stu(state) {
return state.students.filter(s => s.age > 20)
},
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的
# 2、Getters作为参数和传递参数
- 如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写
store
下 index.js
代码:
const store = new Vuex.Store({
// 这五个一般都是固定的,是对象
state: { //保存状态
counter: 1000,
students: [{
id: 110,
name: 'why',
age: 18
},
{
id: 111,
name: 'kobe',
age: 24
},
{
id: 112,
name: 'james',
age: 30
},
{
id: 113,
name: 'curry',
age: 10
}
],
},
getters: { // 可以认为是 store 的计算属性
// getters里面的方法 也会有state参数
powerCounter(state) {
return state.counter * state.counter
},
// 找出多于20岁的学生
// filter(回调函数(当前元素的值))
more20stu(state) {
return state.students.filter(s => s.age > 20)
},
// 找出大于20岁学生的个数
more20stuLength(state, getters) { // Getters 也可以接受其他 getters 作为第二个参数:
// return state.students.filter(s => s.age > 20).length
return getters.more20stu.length
},
},
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
- 比如上面的案例中,我们希望根据ID获取用户的信息
// ..
getter:{
// 找出年龄大于参数age的学生
moreAgeStu(state) { // getters传递参数 只能让getters本身返回另一个函数.
// return function (age) {
// return state.students.filter(s => s.age > age)
// }
return age => {
return state.students.filter(s => s.age > age)
}
}
}
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
App.vue
<h2>----------App内容: getters相关信息----------</h2>
<h2>{{ $store.getters.powerCounter }}</h2>
<h2>{{ $store.getters.more20stu }}</h2>
<h2>{{ $store.getters.more20stuLength }}</h2>
<h2>{{ $store.getters.moreAgeStu(12) }}</h2>
1
2
3
4
5
6
2
3
4
5
6
# 六、Mutation
# 1、Mutation状态更新
- Vuex的store状态的更新唯一方式:提交Mutation
- Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
mutation的定义方式:
mutations:{ increment(state){ state.count++ } }
1
2
3
4
5通过mutation更新
increment:function(){
this.$store.commit('increment')
}
1
2
3
2
3
# 2、Mutation传递参数
- 在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数
- 参数被称为是mutation的载荷(Payload)
- Mutation中的代码:
// ...
mutations: { // 方法 修改state唯一途径 同步操作
// mutations传递参数
// 单个参数
incrementCount(state, count) {
console.log(count);
state.counter +=count
},
},
// ...
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
App.vue代码:
// template... <!-- mutation传递参数 --> <button @click="addCount(5)">+5</button> <button @click="addCount(10)">+10</button> <button @click="addStudent">添加学生</button> // ... // methods addCount(count) { // payload: 负载 // 1.普通的提交封装 这样写的 mutations里的 incrementCount(state, count) 的count就是count this.$store.commit('incrementCount', count) // 单个参数 },
1
2
3
4
5
6
7
8
9
10
11
12
13- 但是如果参数不是一个呢?
- 比如我们有很多参数需要传递.
- 这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象.
- 这个时候可以再从对象中取出相关的信息.
- 但是如果参数不是一个呢?
//mutations
changCount(state,payload){
state.count = payload.count
}
1
2
3
4
5
2
3
4
5
changeCount: function(){
this.$store.commit('changeCount',{ count: 0})
}
1
2
3
2
3
再写个例子:
// ...
mutations: { // 方法 修改state唯一途径 同步操作
// 参数是对象
addStudent(state, stu) {
state.students.push(stu)
},
},
// ...
// App.vue的methods
addStudent() {
// 提交对象
const stu = { id: 114, name: "alan", age: 35 };
this.$store.commit("addStudent", stu);
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3、Mutation 提交风格
- 上面的通过commit进行提交是一种普通的方式
- Vue还提供了另外一种风格, 它是一个包含type属性的对象
- Mutation中的处理方式是将整个commit的对象作为payload使用, 所以代码没有改变, 依然如下:
- 下图是输出了 payload
Mutation代码
// ... mutations:{ // 特殊的提交封装 incrementCount(state, payload) { // console.log(count); state.counter += payload.count }, }
1
2
3
4
5
6
7
8- App.vue代码
addCount(count) {
// payload: 负载
// 1.普通的提交封装 这样写的 mutations里的 incrementCount(state, count) 的count 就是count
// this.$store.commit('incrementCount', count) // 单个参数
// 2.特殊的提交封装 mutations里的 incrementCount(state, count) 的count 是一个对象 写成payload比较合适,通过payload.count取
this.$store.commit({
type: "incrementCount",
count,
});
},
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 4、Mutation响应规则
- Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
- 这就要求我们必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性.
- 当给state中的对象添加新属性时, 使用下面的方式:
- 方式一: 使用 Vue.set(obj, 'newProp', 123)
- 方式二: 用新对象给旧对象重新赋值
- 如何才能让它改变呢?
- 查看下面代码的方式一和方式二
- 都可以让state中的属性是响应式的.
- vuex代码
const store = new Vuex.Store({
state: { //保存状态
// state里面都属性初始化后 每个属性对应一个dep 监听属性变化 dep观察者模式
info: {
name: 'kobe', // Dep -> [Watcher]
age: 40, // Dep -> [Watcher]
height: 1.98 // Dep -> [Watcher]
}
},
mutations: {
updateInfo(state) {
// 可以响应式
state.info.name = 'coderwhy'
// 不能响应式
// state.info['address'] = '洛杉矶' // 不知道为什么只要前面用了可以响应的写法,这个写法也会跟着可以响应
Vue.set(state.info, 'address', '洛杉矶')
// delete state.info.age // “delete+某个属性”该方式做不到响应式
// Vue.delete(state.info, 'age') // Vue.delete() 响应式
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- App.vue
// template
// ...
<h2>----------App内容: info对象的内容是否是响应式----------</h2>
<!-- Mutaions的响应规则 -->
<h2>{{ $store.state.info }}</h2>
<button @click="updateInfo">修改信息</button>
// ...
// methods
updateInfo() {
this.$store.commit('updateInfo')
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 5、Mutation常量类型 – 概念
- 我们来考虑下面的问题:
- 在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
- 当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.
- 方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
- 如何避免上述的问题呢?
- 在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型.
- 我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
- 具体怎么做呢?
- 我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
- 定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.
// 使用mutations时,在commit委托时要写入在mutations里定义时对应的函数名,为防止经常写错
// 可以在 store里创建 mutation-type.js文件,将函数名付给一个常量,再在文件里导入使用
//这样是可以的,不知道为有些地方要用的大写加下划线的写发
export const UPDATE_INFO = 'updateinfo'
1
2
3
4
2
3
4
//mutations.js
[UPDATE_INFO](staet,payload){
//...
}
1
2
3
4
2
3
4
# 6、Mutations常量类型 - 代码
- mutations-type.js代码:
- store下index.js
- App.vue
- mutations-type.js代码:
export const INCREMENT = 'increment'
1
- store下index.js
// import INCREMENT from './mutations-types'// 不能这样导入,只能是export default
// export导出,导入需要加 {}
import {
INCREMENT
} from './mutations-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[INCREMENT](state) {
state.counter++
},
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- App.vue
<script>
import HelloVuex from "./components/HelloVuex";
import { INCREMENT } from "./store/mutations-types";
export default {
name: "App",
methods: {
// 在这里提交方法对应的mutation 的方法名
addition() {
this.$store.commit(INCREMENT);
}
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 7、Mutation同步函数
- 通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
- 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
- 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
- 比如我们之前的代码, 当执行更新时, devtools中会有如下信息: 图1
- 但是, 如果Vuex中的代码, 我们使用了异步函数: 图2
mutations: {
updateInfo(state) {
// 错误的代码: 不能在这里进行异步操作
setTimeout(() => {
state.info.name = 'coderwhy'
}, 1000)
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 你会发现state中的info数据一直没有被改变, 因为他无法追踪到.
- So, 通常情况下, 不要再mutation中进行异步的操作
# 七、Action
# 1、Action的基本定义
- 我们强调, 不要在Mutation中进行异步操作.
- 但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的.这个时候怎么处理呢?
- Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
- Action的基本使用代码如下:
- context是什么? context是和store对象具有相同方法和属性的对象.
- 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
- 但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说.
这样的代码是否多此一举呢?
- 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
- 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.
# 2、Action的分发
- 在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
- 同样的, 也是支持传递payload
App.vue代码:
// ... updateInfo() { // this.$store.commit('updateInfo') // 这样没有经过actions // 异步修改信息 actions // this.$store.dispatch('aUpdateInfo',"我是携带的信息");// 携带一个参数 // 通知外面已经改成功了 -> commit调用之后就算成功,利用对象的方法回调 this.$store.dispatch('aUpdateInfo', { message: '我是携带的信息', success: () => { console.log('里面已经完成了'); } }) }, // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- vuex相关代码
// ...
const store = new Vuex.Store({
mutations:{
updateInfo(state) {
state.info.name = 'coderwhy'
}
},
actions:{
// 默认参数 context: 上下文 现在先理解成store
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updateInfo')
console.log(payload.message);
payload.success() // 调用回调 告诉外面已经成功
}, 1000)
},
}
})
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 这样不够优雅,回调消息与携带信息混在一起,所以下面用Promise
# 3、Action返回的Promise
- 前面我们学习ES6语法的时候说过, Promise经常用于异步操作.
- 在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject.
- OK, 我们来看下面的代码:
vuex代码:
// ... actions:{ // Action返回的Promise aUpdateInfo(context, payload) { return new Promise((resolve, reject) => { setTimeout(() => { context.commit('updateInfo'); console.log(payload); resolve('1111111') }, 1000) }) // then()在App.vue里面写 } } // ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- App.vue :
// ...
updateInfo() {
// 之前写的不够优雅 回调消息与携带信息混在一起,现在用Promise封装再用resolve调用
this.$store.dispatch("aUpdateInfo", "我是携带的信息").then((res) => {
console.log("里面完成了提交");
console.log(res);
});
},
// ...
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 八、Module
补:modules 里的mutations中的方法名不要和store里mutations里的重复
(老师说commit时好像是会先从store里找,再到modules里找)
# 1、认识Module
- Module是模块的意思, 为什么在Vuex中我们要使用模块呢?
- Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
- 当应用变得非常复杂时,store对象就有可能变得相当臃肿.
- 为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等
- 我们按照什么样的方式来组织模块呢?
- 我们来看左边的代码
# 2、Module 局部状态
- 上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写.
- 我们在moduleA中添加state、mutations、getters
- mutation和getters接收的第一个参数是局部状态对象
- 注意:
- 虽然, 我们的 doubleCount 和 increment 都是定义在对象内部的.
- 但是在调用的时候, 依然是通过this.$store来直接调用的.
# 3、Actions的写法
- actions的写法呢? 接收一个context参数对象
- 局部状态通过 context.state 暴露出来,根节点状态则为
context.rootState
- 局部状态通过 context.state 暴露出来,根节点状态则为
- 如果getters中也需要使用全局的状态, 可以接受更多的参数
- getters里的第三个参数为 rootState(module里才需要使用)
- moduleA代码
const moduleA = {
// 组件里面通过$store.state.a.name获取属性
state: {
name: 'zhangsan'
},
// 官网state写法
// state: () => ({
// }),
// 使用 this.$store.commit() 提交
mutations: {
updateName(state, payload) {
state.name = payload
}
},
// $store.getters.fullname调用
getters: {
fullname(state) {
return state.name + '11111'
},
fullname2(state, getters) { // 使用其他getters
return getters.fullname + '2222'
},
// rootState 可以使用大的state里面的值
fullname3(state, getters, rootState) {
return getters.fullname2 + rootState.counter
}
},
// this.$store.dispatch() 提交
actions: {
// 这里的 context 不是store对象了 而是这个module对应的mutations
aUpdateName(context) {
console.log(context);
setTimeout(() => {
context.commit('updateName', 'wangwu')
}, 1000)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
- App.vue
// ...
<h2>----------App内容: modules中的内容----------</h2>
<!-- modules的state -->
<h2>{{ $store.state.a.name }}</h2>
<!-- modules的mutations -->
<button @click="updateName">修改名字</button>
<!-- modules的getters -->
<h2>{{ $store.getters.fullname }}</h2>
<h2>{{ $store.getters.fullname2 }}</h2>
<h2>{{ $store.getters.fullname3 }}</h2>
<!-- modules的actions -->
<button @click="asyncUpdateName">异步修改名字</button>
// ...
// methods
updateName() {
this.$store.commit("updateName", "lisi");
},
asyncUpdateName() {
this.$store.dispatch("aUpdateName");
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
关于state:()=>({})写法
这样写是为了给箭头函数返回 JSON对象,如果不加(),那么{}中的内容将会被当做代码,这样js语法错误。
例如下面这个,moudeA.state()返回{a:1,b:2}
const moduleA = { state: () => ({ a:1,b:2}) }
1
2
3要是不加括号,{a:1,b:2}中a:1,b:2为js代码会出错
const moduleA = { state: () => { a:1,b:2} }
1
2
3
# 九、项目结构组织
# 项目结构
- 当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰.
代码
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'
// 1.安装插件
Vue.use(Vuex)
// 2.创建对象 new Vuex.Store
// state 一般不会抽出去 而是写在这个文件
const state = {
counter: 1000,
students: [{
id: 110,
name: 'why',
age: 18
},
{
id: 111,
name: 'kobe',
age: 24
},
{
id: 112,
name: 'james',
age: 30
},
{
id: 113,
name: 'curry',
age: 10
}
],
info: {
name: 'kobe', // Dep -> [Watcher]
age: 40, // Dep -> [Watcher]
height: 1.98 // Dep -> [Watcher]
}
}
const store = new Vuex.Store({
// 这五个一般都是固定的,是对象
state, //保存状态
mutations, // 方法 修改state唯一途径 同步操作
actions, // 如果有异步操作在这里写 比如网络请求
getters,
modules: {
a: moduleA
}
})
// 3.导出store对象
export default store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
- mutatuions.js
import {
INCREMENT
} from "./mutations-types";
export default {
// 定义方法也可以这样写 ['text'](){}
[INCREMENT](state) {
// 不需要this
state.counter++
},
decrement(state) {
state.counter--
},
// Mutation传递参数
incrementCount(state, payload) { // 多个参数 对象
// console.log(count);
state.counter += payload.count
},
addStudent(state, stu) {
state.students.push(stu)
},
updateInfo(state) {
state.info.name = 'coderwhy'
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- getters.js
export default {
// 也会有state参数
powerCounter(state) {
return state.counter * state.counter
},
more20stu(state) {
return state.students.filter(s => s.age > 20)
},
more20stuLength(state, getters) { // 再多加一个getters参数
// return state.students.filter(s => s.age > 20).length
return getters.more20stu.length
},
moreAgeStu(state) {
return age => {
return state.students.filter(s => s.age > age)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- actions.js
export default {
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo');
console.log(payload);
resolve('1111111')
}, 1000)
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 新建文件夹modules,里面建文件moduleA.js
export default {
state: {
name: 'zhangsan'
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
getters: {
fullname(state) {
return state.name + '11111'
},
fullname2(state, getters) {
return getters.fullname + '2222'
},
fullname3(state, getters, rootState) {
return getters.fullname2 + rootState.counter
}
},
actions: {
aUpdateName(context) {
console.log(context);
setTimeout(() => {
context.commit('updateName', 'wangwu')
}, 1000)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
编辑 (opens new window)
上次更新: 2023/09/12, 14:55:44