在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是 状态管理。
在Vue开发中,我们使用组件化的开发方式;而在组件中我们定义data或者在setup中返回使用的数据, 这些数据我们称之为state;
在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View;
在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions,解决异步操作。
在组件中dispatch调用store中的Action,在store中Action通过context.commit去调用Mutation,在Mutation里调用state.xxx来获取存储在state里的值,对其改变状态。
创建Store
每一个Vuex应用的核心就是store(仓库): store本质上是一个容器,它包含着你的应用中大部分的状态(state);
Vuex和单纯的全局对象有什么区别呢?
-
第一:Vuex的状态存储是响应式的: 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
-
第二:你不能直接改变store中的状态: 改变store中的状态的唯一途径就显示提交
(commit) mutation;
methods: {
increment() {
this.$store.commit('increment')
},
decrement() {
this.$store.commit('decrement')
}
}
这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;
使用步骤:
-
创建Store对象;
-
在app中通过插件安装;
组件中使用store
<template>
<div>
//模板中拿到数据 $store.state.xxxx
<h1>{{ $store.state.counter }}</h1>
<button @click="increment">++</button>
<button @click="decrement">--</button>
</div>
</template>
<script>
export default {
//mutation里的方法
methods: {
increment() {
//调用mutation中的方法对数据进行修改
this.$store.commit('increment')
},
decrement() {
this.$store.commit('decrement')
}
}
}
</script>
//composition API
import { useStore } from 'vuex'
setup() {
const store = useStore() // 使用useStore方法
console.log(store);
return{
store
}
};
单一状态树
-
Vuex 使用单一状态树:用一个对象就包含了全部的应用层级的状态;采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源;这也意味着,每个应用将仅仅包含一个 store 实例;单一状态树和模块化并不冲突,后面我们会讲到module的概念;
-
单一状态树的优势:
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难; 所以Vuex也使用了单一状态树来管理应用层级的全部状态;
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便 的管理和维护;
mapState()
返回的是一个对象
computed: {
//数组写法 ... 展开运算符
...mapState(['counter', 'age', 'name'])
...mapState({
//返回Scounter
Scounter:state => state.counter
})
},
setup中使用mapState()
setup() {
//单个取数据
const sCounter = computed(() => store.state.counter)
return {
sCounter
}
}
默认情况下,Vuex并没有提供非常方便的使用mapState的方式,所以进行了一个函数的封装(来自于王红元老师的封装方法)
getter()
某些属性我们可能需要经过变化后来使用,使用getter()
//获取
<h2>总价值:{{ $store.getters.totalPice }}</h2>
getters: {
totalPice(state) {
let totalPice = 0
//遍历拿到里面的所有item
for (const book of state.books) {
totalPice += book.pice
}
return totalPice
}
},
mapGetters()
computed: {
//数组写法 ... 展开运算符
...mapGetters(['counter', 'age', 'name'])
},
setup() {
//单个取数据
const stotalPice = computed(() => store.getters.totalPice)
return {
stotalPice
}
}
Mutation()
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
接收多个参数
methods: {
increment() {
this.$store.commit('increment', { name: 'why', age: 18 })
},
}
mutations: {
//传多个参数 对象类型 payload
increment(state, payload) {
state.counter++
},
decrement(state) {
state.counter--
}
},
mapMutations()
我们也可以借助于辅助函数,帮助我们快速映射到对应的方法中:
methods: {
//数组方式
...mapMutations(['increment', 'decrement'])
}
import {mapMutations} from 'vuex
setup() {
const mutation = mapMutations(['increment', 'decrement'])
return {
...mutation
}
}
mutation重要原则
开发者工具会记录mutation;
-
每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;
-
但是在mutation中执行异步操作,就无法追踪到数据的变化;
-
所以Vuex的重要原则中要求 mutation必须是同步函数;
actions()
完成异步操作,再通过mutation进行状态的修改
Action提交的是mutation,而不是直接变更状态;
Action可以包含任意异步操作;
context是一个和store实例均有相同方法和属性的context对象;
我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;
在组件中利用dispatch分发store中的操作
methods: {
//组件中通过dispatch分发操作
increment() {
//携带参数
this.$store.dispatch('incrementAction',{name:'why'})
}
}
//store中,payload接收参数,context 上下文 获取mutation里面的同步操作
actions: {
incrementAction(context,payload) {
context.commit('increment')
}
},
mutations: {
increment(state) {
state.counter++
}
},
mapAction()
methods: {
//数组写法
...mapActions(['increment','decrement'])
}
<button @click="incrementAction">++</button>
//setup
import { mapActions } from 'vuex'
setup() {
const action = mapActions(['incrementAction'])
return { ...action }
}
module的基本使用
Vuex 允许我们将 store 分割成模块(module); 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;
//创建home模块
const homeModule = {
state() {
return {
homeCounter: 0
}
},
getters: {},
mutations: {},
actions: {}
}
//导出
export default homeModule
//主仓库
import { createStore } from 'vuex'
//导入仓库模块
import homeModule from './home'
export default createStore({
state() {
return {
rootCounter: 0
}
},
getters: {},
mutations: {},
actions: {},
//模块化
modules: {
home: homeModule
}
})
//组件中使用home模块中的数据
<h2>{{ $store.state.home.homeCounter }}</h2>
module的命名空间
默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:这样使得多个模块能够对同一个action或mutation 作出响应;Getter 同样也默认注册在全局命名空间;
如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true的方式使其成为带命名空间的模块:
当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
const homeModule = {
//开启命名空间
namespaced: true,
}
访问
//取state里的值,加上模块名称
<h2>{{ $store.state.home.homeCounter }}</h2>
//取getter数据,加上模块名称
<h2>Home:{{ $store.getters['home/doubleHomeCounter'] }}</h2>
export default {
methods: {
homeAdd() {
//mutation里面的方法,'模块名称/里面的方法'
this.$store.commit('home/increment')
}
}
}
module修改或派发根组件
模块的辅助函数
<script>
import { mapMutations, mapState ,mapMutations , mapActions } from 'vuex'
export default {
computed: {
//模块名称:[数据]
...mapState('home', ['homeCounter'])
...mapGetter('home', ['homeCounter'])
},
methods: {
...mapMutations('home', ['increment'])
...mapActions('home', ['increment'])
}
}
//composition API
setup() {
const state = mapState(['homeCounter'])
const getter = mapGetter('home', ['homeCounter'])
const mutations = mapMutations(['increment'])
const actions = mapActions('home', ['increment'])
return {
...state,
...getter,
...mutations,
...actions
}
}
</script>
nexttick()
官方解释:将回调推迟到下一个 DOM 更新周期之后执行。
当数据更新了,dom渲染完成后,自动执行该函数,
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个tick才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
nexttick将回调函数推入微任务中,排在所有的微任务后面,当前面的微任务执行完毕,执行该条微任务
const addRef = () => {
message.value += 'hhhhhhh'
nextTick(() => {
console.log(titleRef)
})
}
nextTick和$nextTick区别
1.nextTick(callback):当数据发生变化,更新后执行回调。在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
2.$nextTick(callback):当DOM 发生变化,更新后执行的回调。将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
nextTick(callback)是全局的方法;$nextTick(callback)是回调的 this 自动绑定到调用它的组件实例上;