Vue Vuex中State, Mutations,Actions,Getters使用详解
Vuex 的详细介绍请查看官方文档 :https://vuex.vuejs.org/zh/前言:写这个教程的原因是自己和 Vuex 打交道的时候踩过很多坑,所以记录一下学习的过程,还有是官方的教程真的是太官方了,对我这种小菜鸟来说不是那么的通俗易懂。1、Vuex 是什么Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以
Vuex 的详细介绍请查看官方文档 :https://vuex.vuejs.org/zh/
前言:写这个教程的原因是自己和 Vuex 打交道的时候踩过很多坑,所以记录一下学习的过程,还有是官方的教程真的是太官方了,对我这种小菜鸟来说不是那么的通俗易懂。
1、Vuex 是什么
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
// 也就是说
* VueX是适用于在Vue项目开发时使用的状态管理工具。
// 什么情况下使用
试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。
* 为此,Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX。
// 使用
* 在具有VueX的Vue项目中,我们只需要把这些值定义在VueX中,即可在整个Vue项目的组件中使用。
* 存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,例如状态对象必须是纯粹 (plain) 的
2、核心概念
Vuex 的核心概念由五部分组成:State (存放状态)、Getter (加工state成员给外界)、Mutation (state成员操作)、Action (异步操作)和 Module (模块化状态管理)。
3、 State 单一状态树
state为单一状态树,在state中需要定义我们所需要管理的数组、对象、字符串等等,
只有在这里定义了,在vue.js的组件中才能获取你定义的这个对象的状态
* 用一个对象就包含了全部的应用层级状态。
* 每个应用将仅仅包含一个 store 实例。
至此它便作为一个“唯一数据源 (SSOT)”而存在。
单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
简单理解: 把需要做状态管理的量放到这里,然后在后面的操作中去操作它(比如修改值)
3.1 怎么在 Vue 组件中获得 Vuex 状态?
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
// 简单的 Store 示例代码
//store 文件夹下的index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//这里放全局参数
count: 20201029
},
mutations: {
//这里是set方法
},
getters: {
// 这里是get方法
}
})
// test.vue
<template>
<div class="test">
{{ count }}
</div>
</template>
<script type="text/ecmascript-6">
import store from '../store/index.js'
export default {
name: "test",
data() {
return {
// count:store.state.count
};
},
computed: {
count () {
console.log("count-computed",store.state.count) //20201029
// return store.state.count
}
},
mounted() {
console.log("count-mounted",store.state.count); //20201029
},
};
</script>
<style>
</style>
* 每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用 state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。
* 所以 Vuex 通过 store
选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)
):
// main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex'
import store from './store/index.js';
Vue.use(Vuex)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
components: { App },
template: '<App/>'
})
* 通过在根实例中注册 store
选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store
访问到。
//test.vue
<template>
<div class="test">
{{ this.$store.state.count }}
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: "HelloWorld",
data() {
return {
};
},
computed: {
count () {
console.log("count-computed",this.$store.state.count) //20201029
// return this.$store.state.count
}
},
mounted() {
console.log("count-mounted",this.$store.state.count); //20201029
},
};
</script>
<style>
</style>
3.2 mapState
辅助函数
mapState
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。
所以我们可以使用 mapState 辅助函数来帮助我们生成计算属性:
// 在单独构建的版本中辅助函数为 Vuex.mapState
<template>
<div class="test">
{{count}}
</div>
</template>
<script type="text/ecmascript-6">
import {mapState} from 'vuex'
export default {
name: "test",
data() {
return {
};
},
computed:mapState({
// 箭头函数可使代码更简练
count:state => state.count
}),
mounted() {
},
};
</script>
<style>
</style>
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
comput:mapState([
// 映射 this.count 为 store.state.count
'count'
])
3.3 对象展开运算符
// mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?
通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。
但是自从有了对象展开运算符,我们可以极大地简化写法:
computed:{
localComputed() {/* ... */},
//使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
// 例如:
computed: {
...mapState(["count"]),
},
需要注意的是:
Vuex 的状态固然好用,但是也不要滥用:
* 使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。
* 虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。
* 如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
4、getter
getter有点类似vue.js的计算属性
当我们需要从store的state中派生出一些状态,那么我们就需要使用getter
getter会接收state作为第一个参数,而且getter的返回值会根据它的依赖被缓存起来
只有getter中的依赖值(state中的某个需要派生状态的值)发生改变的时候才会被重新计算。
4.1 * Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
// index.js
export default new Vuex.Store({
state: {
//这里放全局参数
todos: [
{ id: 1, text: '文本1', done: true },
{ id: 2, text: '文本2', done: false }
]
},
// Getter 接受 state 作为其第一个参数:
getters: {
// 这里是get方法
doneTodos: state => {
return state.todos
}
},
})
// test.vue
mounted() {
// 通过属性访问: Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:
console.log(this.$store.getters.doneTodos)
//-> [{ id: 1, text: '文本1', done: true },{ id: 2, text: '文本2', done: false}]
},
4.2 Getter 也可以接受其他 getter 作为第二个参数:
// index.js
export default new Vuex.Store({
state: {
//这里放全局参数
todos: [
{ id: 1, text: '文本1', done: true },
{ id: 2, text: '文本2', done: false }
]
},
getters: {
// 这里是get方法
doneTodos: state => {
return state.todos
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length // 2
}
},
})
// test.vue
<div class="test">
{{this.$store.getters.doneTodosCount}}
</div>
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
},
mounted() {
console.log(this.$store.getters.doneTodosCount) // 2
},
* 注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
4.3 通过方法访问:
可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
// index.js
getters: {
getTodoById:(state) => (id) =>{
return state.todos.find(todo => todo.id === id)
}
},
// test.vue
<template>
<div class="test">
{{this.$store.getters.getTodoById(2)}}
</div>
</template>
<script type="text/ecmascript-6">
import { mapState } from "vuex";
export default {
name: "test",
data() {
return { };
},
computed: {}
},
mounted() {
console.log(this.$store.getters.getTodoById(2)) // { id: 2, text: '文本2', done: false }
},
};
</script>
<style>
</style>
* 注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
4.4 mapGetters
辅助函数
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
// 如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
}
5、 Mutation
* 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
5.1 mutation
是操作state
数据的方法的集合,比如对该数据的修改、增加、删除等等。
// index.js
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '文本1', done: true },
{ id: 2, text: '文本2', done: false }
],
},
mutations: {
increment (state) {
changeName(state) {
// 变更name
state.todos[1].text = "MrBai"
}
}
}
})
// test.vue
<template>
<div class="test">
{{this.$store.getters.getTodoById(2)}}
//点击按钮触发事件后
// { id: 2, text: 'MrBai', done: false }
<button @click="changeName()">修改name</button>
</div>
</template>
<script type="text/ecmascript-6">
import { mapState } from "vuex";
export default {
name: "test",
data() {
return {
};
},
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
},
mounted() {
// console.log(this.$store.getters.getTodoById(2)) // { id: 2, text: '文本2', done: false }
},
methods:{
changeName(){
// 注意,我们不能直接 store.mutations.increment() 来调用,Vuex 规定必须使用 store.commit 来触发对应 type 的方法:
this.$store.commit('changeName')
}
}
};
</script>
<style>
</style>
5.2 提交载荷(Payload):
你可以向 store.commit
传入额外的参数,即 mutation 的 载荷(payload):
//index.js
export default new Vuex.Store({
state: {
count:1
},
mutations: {
//这里是set方法
changeCount(state,num) {
// 变更count
state.count += num
}
},
getters: {
getCount:(state) =>(count) =>{
return state.count
}
}
})
//test.vue
<button @click="changeCount()">修改count</button>
methods:{
changeCount(){
this.$store.commit('changeCount',66)
}
}
5.3 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
mutations: {
//这里是set方法
changeCount(state,num) {
// 变更count
state.count += num.value
}
},
methods:{
changeCount(){
this.$store.commit('changeCount',{
value: 10
})
}
}
5.4 对象风格的提交方式
mutations: {
//这里是set方法
changeCount(state,num) {
// 变更count
state.count += num.value
}
},
methods:{
changeCount(){
this.$store.commit({
type:'changeCount',
value: 10
})
}
}
5.5 Mutation 需遵守 Vue 的响应规则
// 动态修改对象 index.js
export default new Vuex.Store({
state: {
//这里放全局参数
todos: [
{ id: 1, text: '文本1', done: true },
{ id: 2, text: '文本2', done: false }
],
count:1,
people:{
name:'MrBai',
sex:'男'
}
},
mutations: {
//这里是set方法
changeCount(state,num) {
// 给 people 添加一个年龄 age: 20 属性
Vue.set(state.people,'age',20)
// 利用对象展开运算符
state.people = { ...state.people, age: 123 }
// 变更count
state.count += num.value
}
},
getters: {
getPeople:(state)=>(people)=>{
return state.people
}
},
})
// test.vue
<template>
<div class="test">
{{this.$store.getters.getTodoById(2)}}
{{this.$store.getters.getCount()}}
<button @click="changeCount()">修改count</button>
</div>
</template>
<script type="text/ecmascript-6">
import { mapState } from "vuex";
export default {
name: "test",
data() {
return {
};
},
mounted() {
console.log(this.$store.getters.getPeople()) // { age:20,name:'MrBai',sex:'男'}
},
methods:{
changeCount(){
this.$store.commit({
type:'changeCount',
value: 10
})
}
}
};
</script>
<style>
</style>
5.6 Mutation 必须是同步函数
我们之所以要通过提交 mutation 的方式来改变状态数据,是因为我们想要更明确地追踪到状态的变化。如果像下面这样异步的话:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
我们就不知道什么时候状态会发生改变,所以也就无法追踪了,所以强制规定它必须是同步函数。
store.commit('increment')
// 任何由 "increment" 导致的状态变更都应该在此刻完成。
6、Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
actions: {
//Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,(*它与 store 实例有着相同的方法和属性,但是他们并不是同一个实例)
//因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
changeActionsName (context,playload) {
context.commit('changeName',playload)
}
},
// test.vue
methods: {
changeName() {
this.$store.dispatch("changeActionsName",18);
},
},
6.1 Action 就不受约束,可以在 action 内部执行 异步 操作:
// index.js
actions: {
changeActionsName (context,playload) {
setTimeout(() => {
context.commit('changeName',playload)
}, 2000)
}
},
6.2 Actions 支持同样的 载荷方式 和 对象形式 进行分发:
// index.js
actions: {
changeActionsName (context,playload) {
setTimeout(() => {
context.commit('changeName',playload.amount)
}, 2000)
}
},
//test.vue
methods: {
changeName() {
// 以载荷形式分发
this.$store.dispatch("changeActionsName",
{
amount: 18
});
},
// 以对象形式分发
this.$store.dispatch({
type: "changeActionsName",
amount: 18,
});
},
6.3 在组件中分发 Action
在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store
):
import { mapActions } from 'vuex';
methods: {
...mapActions([
// 'changeActionsName', // 将 `this.changeActionsName()` 映射为 `this.$store.dispatch('changeActionsName')`
// `mapActions` 也支持载荷:
'changeActionsName' // 将 `this.changeActionsName(amount)` 映射为 `this.$store.dispatch('changeActionsName', amount)`
]),
changeName() {
this.changeActionsName(18)
},
},
6.4 Action返回的Promise
Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?
首先,你需要明白 store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
store.dispatch('actionA').then(() => {
// ...
})
最后,如果我们利用 async / await,我们可以如下组合 action:
一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
更多推荐
所有评论(0)