状态管理
的概念主要是应用在单页应用SPA(Single Page Application)
中的,是在React/Vue/Angular等现代化的前端框架流行起来之后才有的一个提法,之前的jQuery时代是没有这种概念的。
而所谓的"状态管理",简单来说其实就是对一个全局对象的一系列增删改查的操作。
图书馆里所有人都可以随意进书库借书还书,如果人数不多,这种方式可以提高效率减少流程,一旦人数多起来就容易混乱,书的走向不明确,甚至丢失。
所以需要一个图书管理员来专门记录借书的记录,也就是你要委托图书管理员给你借书及还书。
实际上,大多数状态管理方案都是如上思想,通过管理员(比如 Vuex)去规范书库里书本的借还(项目中需要存储的数据)。
比如说个列表都引用了同一个数据源,用户在其中一张列表上做修改,另外一张列表上的数据也要跟着变。想要实现数据的同步更新,怎么办?
有人可能会说,这简单,用户在第一张表做修改的时候我们发一个消息给第二张表,让它把相应的数据改了不就完事了。但是如果有10张表,20张表呢?都去一张张通知吗?要是其他组件也有用到这个数据源,也都去通知更新吗?
所以这个方案在项目简单的时候或许可行,但是项目一复杂,问题立马就会暴露出来。为了以防未来项目推翻重来,我们还是应该一开始就考虑好。
总之状态管理这个概念,就是为了应对复杂的数据流动。
当今前端最火的三个框架要数Angular、Vue、React,它们全部都用到了组件化、模块化的思想,让前端开发变得更加灵活规范,但其中一个突出问题就是组件之间的状态管理问题。
组件化是好事,但是如果没有处理好组件之间的数据流动
,就会造成项目混乱、维护困难的负面影响。
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式
。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
官网链接_https://v3.vuex.vuejs.org/zh/
简单来说就是:Vue应用中遇到复杂的多组件共享状态时,使用Vuex。
备注:
4. 父子组件间使用props
就可以方便地实现状态共享,但嵌套多层的祖孙组件,又或者是兄弟组件使用props
会很繁琐。
5. 使用事件总线
可以解决(备注1)中的问题,但在事件状态数量很多时,会让代码的可读性变差(代码中有很多emit
和on
我们比较难找到某个状态发生变更的时机),而且无法实现响应式的效果。
其他方式解析…
npm install vuex --save
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
// 存放全局共享的数据
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
new Vue({
el: '#app',
store,
})
src/main.ts中
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import { AxiosRequest } from "./utils/axios";
import { Component } from "vue-property-decorator";
Vue.use(AxiosRequest);
Vue.prototype.$message = message;
Vue.prototype.$event = new Vue(); // 事件总线
Component.registerHooks([
"beforeRouteEnter",
"beforeRouteLeave",
"beforeRouteUpdate"
]);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
以下示例讲解均基于此文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
// 只有 mutations 中定义的函数,才有权利修改 state 中的数据
mutations: {
add(state) {
// 不要在 mutations 函数中,执行异步操作
// setTimeout(() => {
// state.count++
// }, 1000)
state.count++
},
addN(state, step) {
state.count += step
},
sub(state) {
state.count--
},
subN(state, step) {
state.count -= step
}
},
actions: {
addAsync(context) {
setTimeout(() => {
// 在 actions 中,不能直接修改 state 中的数据;
// 必须通过 context.commit() 触发某个 mutation 才行
context.commit('add')
}, 1000)
},
addNAsync(context, step) {
setTimeout(() => {
context.commit('addN', step)
}, 1000)
},
subAsync(context) {
setTimeout(() => {
context.commit('sub')
}, 1000)
},
subNAsync(context, step) {
setTimeout(() => {
context.commit('subN', step)
}, 1000)
}
},
getters: {
showNum(state) {
return '当前最新的数量是【' + state.count + '】'
}
}
})
Vuex
使用单一状态树,即使用一个对象包含全部的应用层级状态。
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
State
就是唯一的公共数据源,所有共享的数据都要统一放到Store
的State
中进行存储。这也意味着,每个应用将仅仅包含一个 store
实例。
通过this.$store.state.全局数据名称
访问,例如:
<h3>当前最新Count值为:{{this.$store.state.count}}h3>
从Vuex
中按需导入mapState
函数,通过导入的mapState
函数,将当前组件需要的全局数据,映射为当前组件的computed
计算属性:
<template>
<div>
<h3>mapState---当前最新的count值为:{{count}}h3>
div>
template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState(['count'])
}
}
script>
Mutations
用于变更存储在Store
中的数据。
mutations
变更Store
数据,不可以直接操作Store
中的数据Store
数据是无法进行监控的)在mutations
中定义函数,如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
// 只有 mutations 中定义的函数,才有权利修改 state 中的数据
mutations: {
add(state) {
state.count++
}
})
定义的函数会有一个默认参数state
,这个就是存储在Store
中的state
对象。
Mutation中不可以执行异步操作,如需异步,请在Actions中处理
在组件中,通过this.$store.commit(方法名)
完成触发,如下:
export default {
methods: {
add() {
// this.$store.state.count++; 错误写法
this.$store.commit("add");
}
}
};
在组件中导入mapMutations
函数
import { mapMutations } from 'vuex'
通过刚才导入的mapMutations
函数,将需要的mutations
函数映射为当前组件的methods
方法:
methods:{
...mapMutations('add','addN'),
// 当前组件设置的click方法
addCount(){
this.add()
}
}
在通过mutations
更新数据的时候,有时候需携带一些额外的参数,此处参数被称为mutations
的载荷payload
。
payload
对应的就是这个参数值,例如:// mutations内的函数
addN(state, step) {
state.count += step
}
// methods
btnHandler2() {
// commit 的作用,就是调用 某个 mutations 函数
this.$store.commit('addN', 3)
}
payload
是一个对象,可以从对象中取出相关的数据,例如:// mutations内的函数
addNum(state, payload) {
state.count += payload.number
}
// methods
addNum() {
this.$store.commit("addNum", {
number: 10,
step: 1
});
}
备注:在大多数情况下,载荷payload
应该是一个对象,这样可以包含多个字段并且记录的 mutations
会更易读。对象风格的提交方式
Actions
类似于Mutations
,但是是用于处理异步任务的,比如网络请求等。
如果通过异步操作变更数据,必须通过Actions
,而不能使用Mutations
,但在Actions
中还是要通过触发Mutations
的方式间接变更数据。
在actions
中定义函数,如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
// 只有 mutations 中定义的函数,才有权利修改 state 中的数据
mutations: {
add(state) {
state.count++
},
actions: {
addAsync(context) {
// 模拟异步操作
setTimeout(() => {
// 在 actions 中,不能直接修改 state 中的数据;
// 必须通过 context.commit() 触发某个 mutations 才行
context.commit('add')
}, 1000)
}
},
})
在actions
中定义的方法,都会有一个默认参数context
:
context
是和store
对象具有相同方法和属性的对象context
进行commit
相关操作,可以获取context.state
数据在index.js
中,添加actions
及对应的方法:
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// 自增
add(state) {
state.count++
}
},
actions: {
addAsync(context) {
setTimeout(() => {
context.commit('add')
}, 1000);
}
}
})
组件中调用:
<script>
export default {
methods: {
addNumSync(){
// dispatch用于触发Actions中的方法
this.$store.dispatch('addAsync')
}
}
};
</script>
在组件中,导入mapActions
函数:
import { mapActions } from 'vuex'
通过刚才导入的mapActions
函数,将需要的actions
函数映射为当前组件的methods
方法:
import { mapActions } from "vuex";
export default {
methods: {
...mapActions(["addAsync"]),
add() {
this.addAsync()
},
}
在导入mapActions
后,可以直接将指定方法绑定在@click
事件上
...mapActions(["addAsync"]),
--------------------------------------------
<button @click="addAsync">+1(异步)button>
该方式也适用于导入的mapMutations
在index.js
的actions
中,增加携带参数方法,如下:
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// 带参数
addNum(state, payload) {
state.count += payload.number
}
},
actions: {
addAsyncParams(context, payload) {
setTimeout(() => {
context.commit('addNum', payload)
}, 1000);
}
}
})
在组件中,调用如下:
methods: {
addNumSyncParams() {
this.$store.dispatch("addAsyncParams", {
number: 100
});
}
}
Promise
经常用于异步操作,在Actions
中,可以将异步操作放在Promise
中,并且在成功或失败后,调用对应的resolve
或reject
示例:
在store/index.js
中,为actions
添加异步方法:
actions: {
loadUserInfo(context){
return new Promise((resolve)=>{
setTimeout(() => {
context.commit('add')
resolve()
}, 2000);
})
}
}
在组件中调用,如下:
methods: {
addPromise() {
this.$store.dispatch("loadUserInfo").then(res => {
console.log("done");
});
}
}
Getters
用于对Store
中的数据进行加工处理形成新的数据,类似于Vue
中的计算属性,getters
的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。Store
中数据发生变化,Getters
的数据也会跟随变化在index.js
中定义getters
getters:{
showNum(state){
return '当前Count值为:'+state.count
}
}
在组件中使用:
<h3>{{ this.$store.getters.showNum }}h3>
在组件中,导入mapGetters
函数
import { mapGetters } from 'vuex'
通过刚才导入的mapGetters
函数,将需要的getters
函数映射为当前组件的computed
方法:
computed: {
...mapGetters(["showNum"])
}
使用时,直接调用即可:
<h3>{{ showNum }}h3>
Modules
是模块的意思,为什么会在Vuex
中使用模块呢?
Vuex
使用单一状态树,意味着很多状态都会交给Vuex
来管理Store
对象就可能变的相当臃肿Vuex
允许我们将store
分割成模块(Module),并且每个模块拥有自己的State、Mutation、Actions、Getters
等在store
目录下,新建文件夹modules
,用于存放各个模块的modules
文件,此处以moduleA
为例。
在modules
文件夹中,新建moduleA.js
,内部各属性state
、mutations
等都和之前一致,注释详见代码,示例如下:
export default {
state: {
name: '张三'
},
actions: {
aUpdateName(context) {
setTimeout(() => {
context.commit('updateName', '张三的新名字')
}, 1000);
}
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
getters: {
fullName(state) {
return state.name + '1号'
},
fullName2(state, getters) {
// 通过getters调用本组方法
return getters.fullName + '2号'
},
fullName3(state, getters, rootState) {
// state代表当前module数据状态,rootState代表根节点数据状态
return getters.fullName2 + rootState.counter
}
}
}
局部状态通过context.state
暴露出来,根节点状态则为context.rootState
在store/index.js
中引用moduleA
,如下:
import Vue from "vue"
import Vuex from "vuex"
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
export default store
这样就通过分模块完成了对状态管理的模块化拆分。
如果项目非常复杂,除了分模块划分外,还可以将主模块的actions
、mutations
、getters
等分别独立出去,拆分成单独的js文件,分别通过export
导出,然后再index.js
中导入使用。
示例:
分别将主模块的actions
、mutations
、getters
独立成js
文件并导出,以actions.js
为例
export default{
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo')
resolve()
}, 1000);
})
}
}
在store/index.js
中,引入并使用,如下:
import Vue from "vue"
import Vuex from "vuex"
import mutations from './mutations'
import actions from './actions' // 引入的actions
import getters from './getters'
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const state = {
counter: 1000,
students: [
{ id: 1, name: '旺财', age: 12 },
{ id: 2, name: '小强', age: 31 },
{ id: 3, name: '大明', age: 45 },
{ id: 4, name: '狗蛋', age: 78 }
],
info: {
name: 'keko'
}
}
const store = new Vuex.Store({
state,
mutations,
getters,
actions, // 引入的actions
modules: {
a: moduleA
}
})
export default store
欢迎点赞收藏⭐留言如有错误敬请指正!
单向数据流
详解vuex
B站vuex视频教程
与ts结合使用—vuex-module-decorators