概述
一、状态管理概述
1.1 什么是状态管理
在组件中定义data
或者在setup中
返回使用的数据,这些数据称之为state
。
在模块template
中我们可以使用这些数据,模块最终会被渲染成DOM
,我们称之为View
。
在模块中我们会产生一些行为事件,处理这些行为事件时,可能会修改state
,这些行为事件称之为actions
。
他们三者之间总是相互影响的。
因此应用程序需要处理各种各样的数据,这些数据需 要保存在我们应用程序中的某一个位置。
对于这些数据的管理就称之为状态管理。其实就是一个数据变成另一个数据的过程。
1.2 不使用状态管理所带来的的问题
-
在
JavaScript
开发的应用程序中:- 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等
- 也包括一些
UI
的状态,比如某些元素是否被选中,是否显示加载动效,当前分页 - 上面的状态数据会随着应用程序的变大越来越难以管理和维护
-
在
Vue3
开发的应用程序中:-
当应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏
-
来自不同视图的行为需要变更同一状态难以完成
-
对于一些简单的状态,确实可以通过
props
的传递或者Provide
的方式来共享状态但是对于复杂的状态管理来说,单纯通过传递和共享的方式是不足以解决问题的
-
1.3 Vuex的思想和作用
由于管理不断变化的state
本身是非常困难的:
-
状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,
View
页面也有可能会引起状态的变化 -
当程序复杂时,
state
在什么时候,因为什么原因而发生变化,发生怎么样的变化,会非常难以控制和追踪
因此,Vuex
将组件的内部状态抽离出来,以一个全局单例的方式来管理这些状态:
-
在这种模式下,组件树构成了一个巨大的 视图
View
-
不管在树的哪个位置,任何组件都能获取状态或者触发行为
-
通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性
这样代码边会变得更加结构 化和易于维护、跟踪。
在Vue2.x
时,它是主流的状态管理工具。现在依然很多项目再使用。
在Vue3.x
时,Vue
官方也推荐使用Pinia
进行状态管理。
二、Vuex的安装
npm run vuex
‘
三、创建Store
-
每一个
Vuex
应用的核心就是store
(仓库): -
store
本质上是一个容器,它包含着你的应用中大部分的状态(state
); -
Vuex
和单纯的全局对象的区别呢Vuex
的状态存储是响应式的- 当
Vue
组件从store
中读取状态的时候,若store
中的状态发生变化,那么相应的组件也会被更新 - 不能直接改变store中的状态
- 改变store中的状态的唯一途径就显示提交 (
commit
)mutation
- 这样就可以方便的跟踪每一个状态的变化,让我们可以通过一些工具帮助我们更好的管理应用的状态
- 改变store中的状态的唯一途径就显示提交 (
-
语法演示:
1)新建一个store文件夹,下面新建一个index.js
import { createStore } from 'vuex' // createStore创建一个状态管理仓库 const store = createStore({ //仓库中定义着一些状态数据 state: () => ({ counter: 100, }), mutations: { increment(state) { state.counter++ }, }) export default store
2)main.js中注册
import { createApp } from 'vue' import App from './App.vue' // 引入store目录下的index.js import store from './store' // use这个store,这样就可以通过$store去拿到状态管理仓库中的数据 createApp(App).use(store).mount('#app')
3)组件中获取状态管理仓库中数据
<template> <div class="app"> <!-- template中使用store中的counter --> <h2>App当前计数: {{ $store.state.counter }}</h2> </div> </template> <script setup> import {useStore} from 'vuex' // js代码中获取store中的counter const store = useStore(); const conut = store.state.counter //调用mutations中的increment函数去修改counter function increment() { store.commit("increment") } </script>
-
在组件中使用
store
,一般有如下几种情况-
在模板中使用
<template> <div class="app"> <!-- template中使用store中的counter --> <h2>App当前计数: {{ $store.state.counter }}</h2> </div> </template>
-
在
setup
中使用<script setup> import {toRefs} from 'vue' import {useStore} from 'vuex' // js代码中获取store中的counter const store = useStore(); // 这么赋值后数据conut不是响应式的,只是个普通js变量 const conut = store.state.counter // 使用toRefs去解构,解构出来的数据还是响应式的 const countRef = toRefs(store.state) //调用mutations中的increment函数去修改counter function increment() { store.commit("increment") } </script>
-
在
options api
中使用,比如computed
<script> export default { computed: { storeCounter() { return this.$store.state.counter } } } </script>
-
四、单一状态树
Vuex
使用单一状态树: 即用一个对象就包含了全部的应用层级的状态。
这也意味着,每个应用将仅仅包含一个store
实例。
单一状态树的优势:
-
如果状态信息是保存到多个
Store
对象中的,那么之后的管理和维护等等都会变得特别困难;所以
Vuex
也使用了单一状态树来管理应用层级的全部状态; -
单一状态树能够让我们最直接的方式找到某个状态的片段;
-
而且在之后的维护和调试过程中,也可以非常方便的管理和维护;
五、获取多个状态数据–mapState的使用
如果有很多个状态都需要获取话,可以使用mapState
的辅助函数:
-
mapState
的方式一:数组类型;- 旧写法
<script> import { mapState } from 'vuex' export default { computed: { ...mapState(["name", "level", "avatarURL"]), } } </script>
-
mapState
的方式二:对象类型;- 旧写法
<script> import { mapState } from 'vuex' export default { computed: { ...mapState({ sName: state => state.name, sLevel: state => state.level }) } } </script>
-
主流写法
mapState
在setup
中并不好用,如果使用组合式API
一般都按下面的方式写
<script setup> import { toRefs } from 'vue' import { useStore } from 'vuex' // 直接对store.state进行解构(推荐) const store = useStore() const { name, level } = toRefs(store.state) function incrementLevel() { store.state.level++ } </script>
从mapState的使用也可以看出来,Vuex适用的是选项式API,也就是适用于Vue2.x
而组合式API中用起来会很别扭,Vue3.x更加契合pinia
六、getters的使用
某些属性可能需要经过变化后来使用,这个时候可以使用getters
6.1 基本使用
getters
可以传入两个参数:
state
:可以用来获取到state
中定义的数据getters
:可以直接获取其它的getters
import { createStore } from 'vuex'
// createStore创建一个状态管理仓库
const store = createStore({
//仓库中定义着一些状态数据
state: () => ({
counter: 100,
name: "张三",
level: 99,
avatarURL: "http://xxxxxx",
friends: [
{ id: 111, name: "why", age: 20 },
{ id: 112, name: "kobe", age: 30 },
{ id: 113, name: "james", age: 25 }
]
}),
getters: {
// getters基本使用
doubleCounter(state) {
return state.counter * 2
},
totalAge(state) {
return state.friends.reduce((preValue, item) => {
return preValue + item.age
}, 0)
},
// 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
}
}
}
})
export default store
<template>
<div class="app">
<h2>doubleCounter: {{ $store.getters.doubleCounter }}</h2>
<h2>friendsTotalAge: {{ $store.getters.totalAge }}</h2>
<h2>message: {{ $store.getters.message }}</h2>
<!-- 根据id获取某一个朋友的信息 -->
<h2>id-111的朋友信息: {{ $store.getters.getFriendById(111) }}</h2>
<h2>id-112的朋友信息: {{ $store.getters.getFriendById(112) }}</h2>
</div>
</template>
其实这里也能看出来,这个getters
和计算属性是非常相似的
6.2 Getters映射–mapGetters的使用
选项式API:
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(["doubleCounter", "totalAge"]),
...mapGetters(["getFriendById"])
}
}
</script>
组合式API:
<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>
七、Mutation的使用
更改Vuex
的store
中的状态的唯一方法是提交mutation
。
7.1 Mutation的基本使用
import { createStore } from 'vuex'
const store = createStore({
state: () => ({
counter: 100,
name: "张三",
level: 100
}),
// 定义可以被调用的mutations
mutations: {
increment(state) {
state.counter++
},
changeName(state, payload) {
state.name = payload
},
incrementLevel(state) {
state.level++
},
// newInfo是传递过来的参数
changeInfo(state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name
}
}
})
export default store
<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 {
methods: {
changeName() {
this.$store.commit("changeName", "王小波")
},
incrementLevel() {
this.$store.commit("incrementLevel")
},
changeInfo() {
this.$store.commit(changeInfo, {
name: "王二",
level: 200
})
}
}
}
</script>
7.2 Mutation定义常量类型
1)抽取常量到一个js文件中
export const CHANGE_INFO = "changeInfo"
2)使用常量
import { createStore } from 'vuex'
import { CHANGE_INFO } from './mutation_types' //导入定义常量的js
const store = createStore({
state: () => ({
name: "张三",
level: 100
}),
// 定义可以被调用的mutations
mutations: {
// newInfo是传递过来的参数
changeInfo(state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name
}
}
})
export default store
<script>
// 导入常量
import { CHANGE_INFO } from "@/store/mutation_types"
export default {
methods: {
changeInfo() {
// 使用常量
this.$store.commit(CHANGE_INFO, {
name: "王二",
level: 200
})
}
}
}
</script>
这也是Vue推荐的做法
7.3 mapMutations辅助函数
选项式API中:
<script>
import { mapMutations } from 'vuex'
import { CHANGE_INFO } from "@/store/mutation_types"
export default {
computed: {
},
methods: {
...mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
}
}
</script>
组合式API中:
<script setup>
import { mapMutations, useStore } from 'vuex'
import { CHANGE_INFO } from "@/store/mutation_types"
const store = useStore()
// 手动的映射和绑定
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>
Vuex确实不太适合在Vue3的组合式API中使用
7.4 mutation重要原则
mutation
必须是同步函数 ,这意味着不可以在mutation
方法中进行异步操作。
-
因为
devtool
工具会记录mutation
的日记; -
每一条
mutation
被记录,devtools
都需要捕捉到前一状态和后一状态的快照; -
但是在
mutation
中执行异步操作,就无法追踪到数据的变化;
是如果我们希望在Vuex
中发送异步·网络请求的话,可以使用actions
八、actions的使用
Action类似于mutation
,不同在于:
Action
提交的是mutation
,而不是直接变更状态,也就是说,想要修改状态数据,必须经过mutation
;Action
可以包含任意异步操作;
8.1 基本使用
import { createStore } from 'vuex'
const store = createStore({
state: () => ({
// 模拟数据
counter: 100,
name: "张三",
level: 100,
}),
mutations: {
increment(state) {
state.counter++
},
},
actions: {
incrementAction(context) {
// console.log(context.commit)
// 用于提交mutation
// console.log(context.getters)
// 用于使用getters
// console.log(context.state)
// 用于使用state
context.commit("increment")
},
changeNameAction(context, payload) {
context.commit("changeName", payload)
},
}
})
export default store
<template>
<div class="home">
<h2>当前计数: {{ $store.state.counter }}</h2>
<button @click="counterBtnClick">发起action修改counter</button>
<h2>name: {{ $store.state.name }}</h2>
<button @click="nameBtnClick">发起action修改name</button>
</div>
</template>
<script>
export default {
methods: {
counterBtnClick() {
// 调用一个action的话 需要使用dispatch方法
this.$store.dispatch("incrementAction")
},
nameBtnClick() {
// 向action中闯入参数的写法
this.$store.dispatch("changeNameAction", "aaa")
}
}
}
</script>
actions
中的函数有一个非常重要的参数context
:
-
context
是一个和store
实例均有相同方法和属性的context
对象; -
context
可以获取到commit
方法来提交一个mutation
,也可以通
context.state
和context.getters
来获取state
和getters
;
使用action
时,进行action
分发的方法:
-
不带参:
this.$store.dispatch("incrementAction")
-
带参:
this.$store.dispatch("changeNameAction", {count:100})
-
直接传递对象:
this.$store.dispatch({ type: "increment", count: 100 })
8.2 actions辅助函数–mapActions
-
对象类型
methods: { ...mapActions( ["increment" , "decrement"]), .. .mapActions({ add : "increment" , sub: "decrement" }) }
-
数组类型
methods: { ...mapActions( ["increment" , "decrement"]) }
-
组合式
API
中的写法<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>
组合式API中使用这个是比较复杂的
8.3 actions中的异步操作
import { createStore } from 'vuex'
const store = createStore({
state: () => ({
// 服务器数据
banners: [],
}),
mutations: {
changeBanners(state, banners) {
state.banners = banners
},
},
actions: {
fetchHomeMultidataAction(context) {
//方式一: Promise链式调用
/*
fetch("http://123.207.32.32:8000/home/multidata").then(res => {
return res.json()
}).then(data => {
console.log(data)
})
*/
// 方式二:
return new Promise(async (resolve, reject) => {
// 3.await/async
const res = await fetch("http://123.207.32.32:8000/home/multidata")
const data = await res.json()
// 修改state数据
context.commit("changeBanners", data.data.banner.list)
resolve("test")
}
}
}
},
modules: {
home: homeModule,
counter: counterModule
}
})
export default store
<template>
<div class="home">
<h2>Home Page</h2>
<ul>
<!-- 使用state中查询出来的数据 -->
<template v-for="item in $store.state.banners" :key="item.acm">
<li>{{ item.title }}</li>
</template>
</ul>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
// 告诉Vuex发起网络请求
const store = useStore()
store.dispatch("fetchHomeMultidataAction").then(res => {
console.log("home中的then被回调:", res)
})
</script>
九、module的使用
由于使用单一状态树,应用的所有状态会集中成一个比较大的对象。
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex
允许我们将 store
分割成模块(module
)。
每个模块拥有自己的 state
、mutation
、action
、getter
、甚至是嵌套子模块.
9.1 基本使用
1)新建一个module目录,下面新建一个home.js
把home.vue
组件中用到的状态数据写到这个js
中去
export default {
state: () => ({
count: 0,
// 服务器数据
banners: []
}),
getters: {
doubleCount(state, getters, rootState) {
return state.count + rootState.rootCounter
}
},
mutations: {
incrementCount(state) {
console.log(state)
state.count++
}
changeBanners(state, banners) {
state.banners = banners
}
},
actions:
incrementCountAction(context) {
context.commit("incrementCount")
}
fetchHomeMultidataAction(context) {
return new Promise(async (resolve, reject) => {
// await/async
const res = await fetch("http://123.207.32.32:8000/home/multidata")
const data = await res.json()
// 修改state数据
context.commit("changeBanners", data.data.banner.list)
resolve("aaaaa")
})
}
}
}
2)store主文件中使用定义好的module
import { createStore } from 'vuex'
// 导入上面的js文件
import homeModule from './modules/home'
const store = createStore({
state: () => ({
rootCounter: 100,
}),
modules: {
home: homeModule, //使用定义好的module
}
})
export default store
3)模板中使用
<template>
<div class="home">
<h2>Home Page</h2>
<!-- 1.使用state时, 是需要state.moduleName.xxx -->
<h2>Counter模块的counter: {{ $store.state.counter.count }}</h2>
<!-- 2.使用getters时, 是直接getters.xxx -->
<h2>Counter模块的doubleCounter: {{ $store.getters.doubleCount }}</h2>
<button @click="incrementCount">count模块+1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
// 告诉Vuex发起网络请求
const store = useStore()
// 派发事件时, 默认也是不需要跟模块名称
// 提交mutation时, 默认也是不需要跟模块名称
function incrementCount() {
store.dispatch("incrementCountAction")
}
</script>
使用模块中的状态数据,都不需要写模块名称。默认这些模块都会合并到主文件当中的。
9.2 module的命名空间
默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的。
这样使得多个模块能够对同一个 action 或 mutation 作出响应; Getter 同样也默认注册在全局命名空间。
但是这样可能会出现各种命名重复的问题。
如果希望模块具有更高的封装度和复用性,可以添加namespaced: true
的方式使其成为带命名空间的模块。
当模块被注册后,它的所有 getter
、action
及 mutation
都会自动根据模块注册的路径调整命名。
export default {
namespaced: true,
//加上这句话它就有了自己的命名空间
state: () => ({
count: 0,
// 服务器数据
banners: []
}),
}
使用时就需要加上模块名,也就是js
文件的文件名:
<template>
<div class="home">
<h2>Counter模块的doubleCounter: {{ $store.getters["counter/doubleCount"] }}</h2>
<div>
</emplate>
9.3 module修改或派发根组件
就是模块中调用和修改根组件当中的东西。
//这里一共有六个参数
changeNameAction({commit,dispatch, state, rootState, getters, rootGetters}) {
commit( "changeName", "kobe");
// 重要的就是加上root: true即可
commit( "changeRootName", null, {root: true});
dispatch("changeRootNameAction", null, {root: true})
}
最后
以上就是时尚面包为你收集整理的Vue状态管理--Vuex使用详解的全部内容,希望文章能够帮你解决Vue状态管理--Vuex使用详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复