Vuex的使用
安装 npm install vuex
创建Store
- 每一个Vuex应用的核心就是store(仓库):
- store本质上是一个容器,它包含着你的应用中大部分的状态(state)
- Vuex和单纯的全局对象有什么区别呢?
- 第一:Vuex的状态存储是响应式的
- 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
- 第二:你不能直接改变store中的状态
- 改变store中的状态的唯一途径就显示提交 (commit) mutation;
- 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;
- 使用步骤:
组件中使用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: { message(state, getters) { return `name:${state.name} level:${state.level} friendTotalAge:${getters.totalAge}` }, 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
},
},
|
- **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的辅助函数
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,接收的第一个参数是模块的局部状态对象: