在开发中,我们会用应用程序处理很多的数据,这些数据需要保存在我们应用程序的某一个位置,对于这些数据的管理我们就称之为状态管理。
Vuex是一个专门为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,解决多组件数据通信问题。
一、Vuex核心
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果你不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window)就足够你所需了。但是,如果你需要构建一个中大型单页应用,你很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
VueX是采用集中式管理组件依赖的共享数据的一个工具,可以解决不同组件数据共享问题。
1、State:
包含了store中存储的各个数据。
用法:
const store = new Vuex.Store({
state:{
count:0
}
})
组件访问State中数据的三种方式:
- this.$store.state.count
<template>
<div> state的数据:{{ $store.state.count }}</div>
<template>
<script>
// 安装完 vuex 会在组件中注入一个 $store 的变量
created() {
console.log(this.$store.state.count);
}
</script>
- 使用计算属性computed
<template>
<div> state的数据:{{ count }}</div>
<template>
<script>
// 把state中数据,定义在组件内的计算属性中
computed: {
count () {
return this.$store.state.count
}
}
</script>
每当 $store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。
- 借助mapState辅助函数映射
<template>
<div> state的数据:{{ count }}</div>
<template>
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['count'])
}
}
2、Getter
除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters。类似于Vue中的计算属性,根据getter或者state计算返回值。
例如:对列表进行过滤并计数
const store = new Vuex.Store({
state: {
count: [1,2,3,4,5,6,7,8,9,10]
},
getters: {
// getters函数的第一个参数是 state
// 必须要有返回值
getCount: state => state.list.filter(item => item > 5)
}
})
组件访问getters中数据的两种方式:
- this.$store.getters.getCount
<template>
<div>
<div> state的数据:{{ getCount }}</div>
</div>
<template>
<script>
// 把state中数据,定义在组件内的计算属性中
computed: {
getCount () {
return this.$store.getters.getCount
}
}
</script>
- 借助mapGetters辅助函数映射
import {mapGetters} from 'vuex'
export default{
computed:{
...mapGetters(['getCount'])
}
}
3、Mutation
Vuex里store状态更新的唯一方法,且必须是同步函数。
用法:
const store = new Vuex.Store({
state:{
count:0
},
mutations:{
// 接受 state作为第一个参数
mutationCount(state){
state.count ++ ;
}
// 向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)
addCount (state, payload) {
state.count += payload.data
}
}
})
组件访问mutations中数据的两种方式:
- 使用store.commit调用mutation。
methods:{
changeCount(){
this.$store.commit('mutationCount')
}
}
- 借助mapMutations辅助函数映射
import {mapMutations} from 'vuex'
export default{
methods:{
...mapMutations(['mutationCount'])
}
}
在 mutation 中混合异步调用会导致你的程序很难调试。
mutations: {
someMutation (state) {
setTimeout(() => {
state.count++
},1000)
}
}
现在想象,我们正在debug一个app并且观察 devtool中的mutation日志。每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
所以,在 Vuex 中mutation 都是同步函数。
4、Action
用于提交mutation,而不是直接变更状态。可异步。
Action类似于Mutation,不同的在于:
(1)Action提交的是Mutation,而不是直接变更状态
(2)Action可以包含任何异步操作
用法:
const store = new Vuex.Store({
state:{
count:0
},
mutations:{
mutationCount(state,payload){
state.count = payload;
}
},
actions:{
actionCount(context,payload){
setTimeout(()=>{
context.commit('mutationCount',payload.count)
},1000)
}
}
})
组件访问actions中数据的两种方式:
- 使用store.dispatch来分发action。
methods:{
changeCountAsync(){
this.$store.dispatch('actionCount',res)
}
}
- 借助mapActions辅助函数映射
import {mapActions} from 'vuex'
export default{
methods:{
...mapActions(['actionCount'])
}
}
最后,如果我们利用 async / await ,我们可以组合 action:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
5、 Module
允许将单一的Store拆分为多个store。
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
每个模块都拥有独立的state、getters、mutations、actions
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。
二、Vue3中使用Vuex
1. 安装Vuex。
npm install vuex --save
2. 配置Vuex。
在src/store/index.js中配置
import {createStore} from 'vuex';
export default createStore({
})
3. 在main.js中引入Vuex。
import {createApp} from 'vue';
import App from './App.vue';
import {store} from './store';
createApp(App).use(store).mount('#app);
4. 在组件中读取数据。
三、Vuex数据响应式原理
Vuex完美的结合了Vue的响应式数据。那么它的原理是什么呢?
每一个Vue的插件,都需要一个公开的install方法,Vuex也不例外。
// src/store.js
export function install (_Vue) {
if (Vue && _Vue === Vue) {
return
}
Vue = _Vue
applyMixin(Vue)//在所有组件的beforeCreate注入了设置this.$store这样的一个对象,
//在任意组件中执行this.$store都能找到store对象
}
Vuex.Store的构造函数中,有一堆初始化以及resetStoreVM(this,state)整个Vuex的关键。
// src/store.js
function resetStoreVM (store, state, hot) {
// 省略无关代码
Vue.config.silent = true
//本质:将我们传入的state作为一个隐藏的vue组件的data
//也就是说,我们的commit操作,本质上其实是修改这个组件的data
store._vm = new Vue({
data: {
$state: state
},
computed
})
}
//this.$store._vm.$data.$state===this.$store.state
Vuex的state状态是响应式,是借助Vue的data是响应式,将state存入Vue实例组件的data中;Vuex的getters则是借助Vue的计算属性computed实现数据实时监听。
computed计算属性监听data数据变更主要经历以下几个过程:
四、Vuex适用场景
1. Vuex一般用于中大型web单页应用中对应用的状态进行管理。
2. Vuex解决非父子组件之间的通信问题。
3. Vuex做为数据存储中心。如购物车、登陆状态。
五、总结
Vuex是针对Vuejs的状态管理工具。Vuex完美的结合了Vue的响应式数据。
Vue可以直接触发methods中的方法,Vuex不行。为了处理异步,当你触发一个点击事件时,会通过dispatch来访问actions中的方法,actions中的commit会触发mutations中的方法从而修改state的值,通过getters来把数据反映到视图。
Vuex通过Vue.use(Vuex),从而调用install方法,通过applyMixin(Vue)在任意组件执行this.$store都能访问到store对象,实现将store挂载注入到组件中。
Vuex的state状态是响应式的,是借助Vue的data是响应式,将state存入Vue实例组件的data中。
Vuex的getters则是借助于Vue的计算属性computed实现数据的实时监听。