Vuex的使用

安装 npm install vuex

创建Store

  • 每一个Vuex应用的核心就是store(仓库):
    • store本质上是一个容器,它包含着你的应用中大部分的状态(state)
  • Vuex和单纯的全局对象有什么区别呢?
  • 第一:Vuex的状态存储是响应式的
    • 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
  • 第二:你不能直接改变store中的状态
    • 改变store中的状态的唯一途径就显示提交 (commit) mutation;
    • 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;
  • 使用步骤:
    • 创建Store对象;
    • 在app中通过插件安装;

组件中使用store

  • 在组件中使用store,我们按照如下的方式:
    • 在模板中使用;
    • 在options api中使用,比如computed;
    • 在setup中使用;

组件获取状态

  • 在前面我们已经学习过如何在组件中获取状态了。
  • 当然,如果觉得那种方式有点繁琐(表达式过长),我们可以使用计算属性:
  • 但是,如果我们有很多个状态都需要获取话,可以使用mapState的辅助函数:
    • mapState的方式一:对象类型;
    • mapState的方式二:数组类型;
    • 也可以使用展开运算符和来原有的computed混合在一起;
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
<div class="app">
<button @click="incrementLevel">修改level</button>
<!-- 1.在模板中直接使用多个状态 -->
<h2>name: {{ $store.state.name }}</h2>
<h2>level: {{ $store.state.level }}</h2>
<h2>avatar: {{ $store.state.avatarURL }}</h2>

<!-- 2.计算属性(映射状态: 数组语法) -->
<!-- <h2>name: {{ name() }}</h2>
<h2>level: {{ level() }}</h2> -->

<!-- 3.计算属性(映射状态: 对象语法) -->
<!-- <h2>name: {{ sName }}</h2>
<h2>level: {{ sLevel }}</h2> -->

<!-- 4.setup计算属性(映射状态: 对象语法) -->
<!-- <h2>name: {{ cName }}</h2>
<h2>level: {{ cLevel }}</h2> -->

<!-- 5.setup计算属性(映射状态: 对象语法) -->
<h2>name: {{ name }}</h2>
<h2>level: {{ level }}</h2>
</div>
</template>

<script>
import { mapState } from 'vuex'

export default {
computed: {
fullname() {
return "xxx"
},
// name() {
// return this.$store.state.name
// },
...mapState(["name", "level", "avatarURL"]),
...mapState({
sName: state => state.name,
sLevel: state => state.level
})
}
}
</script>

<script setup>
import { computed, toRefs } from 'vue'
import { mapState, useStore } from 'vuex'
import useState from "../hooks/useState"

// 1.一步步完成
// const { name, level } = mapState(["name", "level"])
// const store = useStore()
// const cName = computed(name.bind({ $store: store }))
// const cLevel = computed(level.bind({ $store: store }))

// 2.使用useState
// const { name, level } = useState(["name", "level"])

// 3.直接对store.state进行解构(推荐)
const store = useStore()
const { name, level } = toRefs(store.state)

function incrementLevel() {
store.state.level++
}

</script>

<style scoped>
</style>


useState.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { computed } from 'vue'
import { useStore, mapState } from 'vuex'

export default function useState(mapper) {
const store = useStore()
const stateFnsObj = mapState(mapper)

const newState = {}
Object.keys(stateFnsObj).forEach(key => {
newState[key] = computed(stateFnsObj[key].bind({ $store: store }))
})

return newState
}


getters的基本使用

某些属性我们可能需要经过变化后来使用,这个时候可以使用getters:

getters第二个参数 和返回函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
getters: {

// 2.在该getters属性中, 获取其他的getters
message(state, getters) {
return `name:${state.name} level:${state.level} friendTotalAge:${getters.totalAge}`
},
// 3.getters是可以返回一个函数的, 调用这个函数可以传入参数(了解)
getFriendById(state) {
return function(id) {
const friend = state.friends.find(item => item.id === id)
return friend
}
}
},
1
2
3
4
<h2>message: {{ $store.getters.message }}</h2>

<!-- 根据id获取某一个朋友的信息 -->
<h2>id-111的朋友信息: {{ $store.getters.getFriendById(111) }}</h2>

mapGetters的辅助函数

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
<template>
<div class="app">
<button @click="changeAge">修改name</button>

<h2>doubleCounter: {{ doubleCounter }}</h2>
<h2>friendsTotalAge: {{ totalAge }}</h2>
<h2>message: {{ message }}</h2>

<!-- 根据id获取某一个朋友的信息 -->
<h2>id-111的朋友信息: {{ getFriendById(111) }}</h2>
<h2>id-112的朋友信息: {{ getFriendById(112) }}</h2>
</div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
computed: {
...mapGetters(["doubleCounter", "totalAge"]),
...mapGetters(["getFriendById"])
}
}
</script>

<script setup>

import { computed, toRefs } from 'vue';
import { mapGetters, useStore } from 'vuex'

const store = useStore()

// 1.使用mapGetters
// const { message: messageFn } = mapGetters(["message"])
// const message = computed(messageFn.bind({ $store: store }))

// 2.直接解构, 并且包裹成ref
// const { message } = toRefs(store.getters)

// 3.针对某一个getters属性使用computed
const message = computed(() => store.getters.message)

function changeAge() {
store.state.name = "kobe"
}

</script>

<style scoped>
</style>


Mutation基本使用

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

  • **Mutation携带数据 **
    • 很多时候我们在提交mutation的时候,会携带一些数据,这个时候我们可以使用参数:
    • payload为对象类型
    • 对象风格的提交方式
  • **Mutation常量类型 **
    • ** 定义常量:mutation-type.js **
    • 定义mutation
    • 提交mutation
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
<template>
<div class="app">
<button @click="changeName">修改name</button>
<button @click="incrementLevel">递增level</button>
<button @click="changeInfo">修改info</button>
<h2>Store Name: {{ $store.state.name }}</h2>
<h2>Store Level: {{ $store.state.level }}</h2>
</div>
</template>

<script>

import { CHANGE_INFO } from "@/store/mutation_types"

export default {
computed: {
},
methods: {
changeName() {
// this.$store.state.name = "李银河"
this.$store.commit("changeName", "王小波")
},
incrementLevel() {
this.$store.commit("incrementLevel")
},
changeInfo() {
this.$store.commit(CHANGE_INFO, {
name: "王二",
level: 200
})
}
}
}
</script>

<script setup>

</script>

<style scoped>
</style>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mutations: {
increment(state) {
state.counter++
},
changeName(state, payload) {
state.name = payload
},
incrementLevel(state) {
state.level++
},
[CHANGE_INFO](state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name

// 重要的原则: 不要在mutation方法中执行异步操作
// fetch("xxxx").then(res => {
// res.json().then(res => {
// state.name = res.name
// })
// })
},

},
  • **mapMutations辅助函数 **
  • ** mutation重要原则 **
    • 一条重要的原则就是要记住 mutation 必须是同步函数
      • 这是因为devtool工具会记录mutation的日记;
      • 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;
      • 但是在mutation中执行异步操作,就无法追踪到数据的变化;
    • 所以Vuex的重要原则中要求 mutation必须是同步函数;
      • 但是如果我们希望在Vuex中发送网络请求的话需要如何操作呢?
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
<template>
<div class="app">
<button @click="changeName('王小波')">修改name</button>
<button @click="incrementLevel">递增level</button>
<button @click="changeInfo({ name: '王二', level: 200 })">修改info</button>
<h2>Store Name: {{ $store.state.name }}</h2>
<h2>Store Level: {{ $store.state.level }}</h2>
</div>
</template>

<script>
import { mapMutations } from 'vuex'
import { CHANGE_INFO } from "@/store/mutation_types"

export default {
computed: {
},
methods: {
btnClick() {
console.log("btnClick")
},
// ...mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
}
}
</script>

<script setup>

import { mapMutations, useStore } from 'vuex'
import { CHANGE_INFO } from "@/store/mutation_types"

const store = useStore()

// 1.手动的映射和绑定
const mutations = mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
const newMutations = {}
Object.keys(mutations).forEach(key => {
newMutations[key] = mutations[key].bind({ $store: store })
})
const { changeName, incrementLevel, changeInfo } = newMutations

</script>

<style scoped>
</style>


actions的基本使用

  • Action类似于mutation,不同在于:
    • Action提交的是mutation,而不是直接变更状态;
    • Action可以包含任意异步操作;
  • 这里有一个非常重要的参数context:
    • context是一个和store实例均有相同方法和属性的context对象;
    • 所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;
  • 但是为什么它不是store对象呢?这个等到我们讲Modules时再具体来说;

actions的分发操作

  • 如何使用action呢?进行action的分发:
    • 分发使用的是 store 上的dispatch函数;
  • 同样的,它也可以携带我们的参数
  • 也可以以对象的形式进行分发:

actions的辅助函数

  • action也有对应的辅助函数:
    • 对象类型的写法;
    • 数组类型的写法;
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
<template>
<div class="home">
<h2>当前计数: {{ $store.state.counter }}</h2>
<button @click="incrementAction">发起action修改counter</button>
<button @click="increment">递增counter</button>
<h2>name: {{ $store.state.name }}</h2>
<button @click="changeNameAction('bbbb')">发起action修改name</button>
</div>
</template>

<script>
import { mapActions } from 'vuex'

export default {
methods: {
// counterBtnClick() {
// this.$store.dispatch("incrementAction")
// },
// nameBtnClick() {
// this.$store.dispatch("changeNameAction", "aaa")
// }
// ...mapActions(["incrementAction", "changeNameAction"])
}
}
</script>

<script setup>

import { useStore, mapActions } from 'vuex'

const store = useStore()

// 1.在setup中使用mapActions辅助函数
// const actions = mapActions(["incrementAction", "changeNameAction"])
// const newActions = {}
// Object.keys(actions).forEach(key => {
// newActions[key] = actions[key].bind({ $store: store })
// })
// const { incrementAction, changeNameAction } = newActions

// 2.使用默认的做法
function increment() {
store.dispatch("incrementAction")
}

</script>

<style scoped>
</style>


actions的异步操作

  • Action 通常是异步的,那么如何知道 action 什么时候结束呢?
    • 我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作

module的基本使用

  • 什么是Module?
    • 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃 肿;
    • 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module);
    • 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;

module的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象