在组件中定义data
或者在setup中
返回使用的数据,这些数据称之为state
。
在模块template
中我们可以使用这些数据,模块最终会被渲染成DOM
,我们称之为View
。
在模块中我们会产生一些行为事件,处理这些行为事件时,可能会修改state
,这些行为事件称之为actions
。
他们三者之间总是相互影响的。
因此应用程序需要处理各种各样的数据,这些数据需 要保存在我们应用程序中的某一个位置。
对于这些数据的管理就称之为状态管理。其实就是一个数据变成另一个数据的过程。
在JavaScript
开发的应用程序中:
UI
的状态,比如某些元素是否被选中,是否显示加载动效,当前分页在Vue3
开发的应用程序中:
当应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏
来自不同视图的行为需要变更同一状态难以完成
对于一些简单的状态,确实可以通过props
的传递或者Provide
的方式来共享状态
但是对于复杂的状态管理来说,单纯通过传递和共享的方式是不足以解决问题的
由于管理不断变化的state
本身是非常困难的:
状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View
页面也有可能会引起状态的变化
当程序复杂时,state
在什么时候,因为什么原因而发生变化,发生怎么样的变化,会非常难以控制和追踪
因此,Vuex
将组件的内部状态抽离出来,以一个全局单例的方式来管理这些状态:
在这种模式下,组件树构成了一个巨大的 视图View
不管在树的哪个位置,任何组件都能获取状态或者触发行为
通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性
这样代码边会变得更加结构 化和易于维护、跟踪。
在Vue2.x
时,它是主流的状态管理工具。现在依然很多项目再使用。
在Vue3.x
时,Vue
官方也推荐使用Pinia
进行状态管理。
npm run vuex
‘
每一个Vuex
应用的核心就是store
(仓库):
store
本质上是一个容器,它包含着你的应用中大部分的状态(state
);
Vuex
和单纯的全局对象的区别呢
Vuex
的状态存储是响应式的Vue
组件从store
中读取状态的时候,若store
中的状态发生变化,那么相应的组件也会被更新commit
) mutation
语法演示:
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)组件中获取状态管理仓库中数据
App当前计数: {{ $store.state.counter }}
在组件中使用store
,一般有如下几种情况
在模板中使用
App当前计数: {{ $store.state.counter }}
在setup
中使用
在options api
中使用,比如computed
Vuex
使用单一状态树: 即用一个对象就包含了全部的应用层级的状态。
这也意味着,每个应用将仅仅包含一个store
实例。
单一状态树的优势:
如果状态信息是保存到多个Store
对象中的,那么之后的管理和维护等等都会变得特别困难;
所以Vuex
也使用了单一状态树来管理应用层级的全部状态;
单一状态树能够让我们最直接的方式找到某个状态的片段;
而且在之后的维护和调试过程中,也可以非常方便的管理和维护;
如果有很多个状态都需要获取话,可以使用mapState
的辅助函数:
mapState
的方式一:数组类型;
mapState
的方式二:对象类型;
主流写法
mapState
在setup
中并不好用,如果使用组合式API
一般都按下面的方式写
从mapState的使用也可以看出来,Vuex适用的是选项式API,也就是适用于Vue2.x
而组合式API中用起来会很别扭,Vue3.x更加契合pinia
某些属性可能需要经过变化后来使用,这个时候可以使用getters
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
doubleCounter: {{ $store.getters.doubleCounter }}
friendsTotalAge: {{ $store.getters.totalAge }}
message: {{ $store.getters.message }}
id-111的朋友信息: {{ $store.getters.getFriendById(111) }}
id-112的朋友信息: {{ $store.getters.getFriendById(112) }}
其实这里也能看出来,这个getters
和计算属性是非常相似的
选项式API:
组合式API:
更改Vuex
的store
中的状态的唯一方法是提交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
Store Name: {{ $store.state.name }}
Store Level: {{ $store.state.level }}
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
这也是Vue推荐的做法
选项式API中:
组合式API中:
Vuex确实不太适合在Vue3的组合式API中使用
mutation
必须是同步函数 ,这意味着不可以在mutation
方法中进行异步操作。
因为devtool
工具会记录mutation
的日记;
每一条mutation
被记录,devtools
都需要捕捉到前一状态和后一状态的快照;
但是在mutation
中执行异步操作,就无法追踪到数据的变化;
是如果我们希望在Vuex
中发送异步·网络请求的话,可以使用actions
Action类似于mutation
,不同在于:
Action
提交的是mutation
,而不是直接变更状态,也就是说,想要修改状态数据,必须经过mutation
;Action
可以包含任意异步操作;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
当前计数: {{ $store.state.counter }}
name: {{ $store.state.name }}
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
})
对象类型
methods: {
...mapActions( ["increment" , "decrement"]),
.. .mapActions({
add : "increment" ,
sub: "decrement"
})
}
数组类型
methods: {
...mapActions( ["increment" , "decrement"])
}
组合式API
中的写法
组合式API中使用这个是比较复杂的
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
Home Page
- {{ item.title }}
由于使用单一状态树,应用的所有状态会集中成一个比较大的对象。
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex
允许我们将 store
分割成模块(module
)。
每个模块拥有自己的 state
、mutation
、action
、getter
、甚至是嵌套子模块.
把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")
})
}
}
}
import { createStore } from 'vuex'
// 导入上面的js文件
import homeModule from './modules/home'
const store = createStore({
state: () => ({
rootCounter: 100,
}),
modules: {
home: homeModule, //使用定义好的module
}
})
export default store
Home Page
Counter模块的counter: {{ $store.state.counter.count }}
Counter模块的doubleCounter: {{ $store.getters.doubleCount }}
使用模块中的状态数据,都不需要写模块名称。默认这些模块都会合并到主文件当中的。
默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的。
这样使得多个模块能够对同一个 action 或 mutation 作出响应; Getter 同样也默认注册在全局命名空间。
但是这样可能会出现各种命名重复的问题。
如果希望模块具有更高的封装度和复用性,可以添加namespaced: true
的方式使其成为带命名空间的模块。
当模块被注册后,它的所有 getter
、action
及 mutation
都会自动根据模块注册的路径调整命名。
export default {
namespaced: true, //加上这句话它就有了自己的命名空间
state: () => ({
count: 0,
// 服务器数据
banners: []
}),
}
使用时就需要加上模块名,也就是js
文件的文件名:
Counter模块的doubleCounter: {{ $store.getters["counter/doubleCount"] }}
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})
}