什么是Vuex
Vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用
集中式存储管理
应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。——官方介绍
对于第一次接触Vuex的新手来说,看官方介绍多少还是有些迷糊。通俗一点来说,Vuex就是状态管理
工具、数据管理
工具,帮我们的应用管理着共享状态,解决了组件之间共享同一状态的麻烦问题的一个插件。
为什么要使用Vuex
可以使用的组件间传值方案
- 通过路由我们可以携带一些简单的参数;
- 使用prop和$emit来进行通讯;
- 使用EventBus(事件总线);
- 更多的其他方式...
遇到的问题
- 我们先来描述一种场景,当项目中多个组件都依赖同一个状态的时候,如果是多层嵌套的父子组件,使用prop和$emit来进行传递会显得颇为繁琐;如果是兄弟组件要想进行传递,你或许可以使用EventBus(事件总线)来进行交互,不得不承认,它非常的灵活,但是当事件变多了以后维护起来会变得颇为麻烦,而且这种发布者-订阅者的模式下,发布于订阅必须是成对出现的,在订阅事件的组件里,必须要手动销毁监听,不然会多次执行。
Parent.vue
<template>
<div>
<h3>我是父组件,今年我{{ parentAge }}岁了!</h3>
<Child :age="parentAge" @addParentAge="handlerAge"></Child>
</div>
</template>
<script>
import Child from './Child'
export default {
name:'Parent',
components:{
Child
},
data () {
return {
parentAge: 50
}
},
methods: {
handlerAge(age) {
alert('好的,我知道了我的实际年龄是:', age)
}
}
}
</script>
Child.vue
<template>
<div>
<h3>我是子组件,父组件告诉我他今年{{ age }}岁了!</h3>
实际上,父组件今年的生日已经过了,所以他的年龄应该
<button @click="addAge">加1</button>
</div>
</template>
<script>
export default {
name:'Child',
props: {
age: { type: Number, default: 0 }
},
methods: {
addAge() {
this.$emit('addParentAge', this.age + 1);
}
}
}
</script>
- 有时候我们通过路由来传递某一种或者多种状态(数据),如果状态过多或某些状态的内容过长的时候,这种方式显然也行不通了。
navTo("/pages/discover/sub/activity-detail", { id: id });
- 随着项目越来越复杂,组件和数据之间的对应关系也变得愈加混乱。
那么Vuex可以解决我们遇到的这些问题么?接下来我们试一下吧!
怎么使用Vuex
安装
npm install vuex --save // 使用 npm
yarn add vuex // 使用 yarn
当然,你可以可以直接下载 vuex 或者CDN引用的方式来使用
<script src="/path/vue.js"></script>
<script src="/path/vuex.js"></script>
目录和文件
在 src 目录下按照你的需求构建 store 目录以及 js文件,我们可以在这里组装模块并导出 store
使用介绍
一、State
引入 Vuex 后,我们需要在 state 中定义变量,感觉这个有点类似 Vue 中的 data,通过 state 来存放状态。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 存放状态
state: {
age: 51,
name: 'Parent',
nickName: '父组件'
},
mutations: {
},
actions: {
},
modules: {
}
})
这样的话就不用考虑组件间的传值,直接通过 $store.state.age 来获取年龄数据
<template>
<div>
<h3>我是子组件,父组件告诉我他今年{{ $store.state.age }}岁了!</h3>
</div>
</template>
当然,我们也可以把它定义在计算属性 computed 中
template>
<div>
<h3>我是子组件,父组件告诉我他今年{{ age }}岁了!</h3>
</div>
</template>
<script>
export default {
name:'Child',
computed: {
age() {
return this.$store.state.age
}
}
}
</script>
mapState
上面的代码,从使用上来说基本可以满足我们的需求,但是属性多起来的话,感觉代码写得还是有点繁琐,好在 Vuex 还给我们提供了简便方法 mapState
import { mapState } from 'vuex'
export default {
name:'Child',
computed: mapState(['age', 'nickName', 'name'])
}
computed: mapState(['age', 'nickName', 'name'])
不得不说,真香!上面这一句代码的作用,等同于下面这段代码:
computed: {
age() {
return this.$store.state.age
}
nickName() {
return this.$store.state.nickName
}
name() {
return this.$store.state.name
}
}
作为一个新手,可能还会有个疑问,你这样写的话,如果我想再新增一个自定义的计算属性怎么办呢?虽然让我少写了几行代码,但是确影响到我继续添加计算属性了。别慌,经过参考项目中大佬的代码发现是使用了 ES6 的展开运算符“...”
computed: {
realAge() {
return this.age + 1
}
...mapState(['age', 'nickName', 'name'])
}
二、Getters
从各种资料以及个人的实际操作过程中,可以这样来理解,getters 相当于 Vue 中的计算属性,通过 getters 可以获取我们想要取得的值。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 存放状态
state: {
age: 51,
name: 'Parent',
nickName: '父组件'
},
getters: {
desc(state) {
return state.name + '的年龄是' + state.age
}
},
mutations: {
},
actions: {
},
modules: {
}
})
然后可以在 Vue 这样使用它: <div>{{ desc }}</div>
computed: {
realAge() {
return this.age + 1
}
...mapGetters(['desc'])
}
三、Mutations
通过提交 mutation 可以实现更改 Vuex 的 store 中的状态。有没有觉得 Mutations 有点类似 Vue 的 methods 事件?每一个 mutation 都有一个字符串的事件类型(type)和一个回调函数,这个回调函数也就是我们进行状态更改的地方,它可以传入参数,第一个参数时 state,第二个参数时 payload,直接来看看代码吧!
mutations: {
addAge(state, payload) {
state.age += payload.years
}
},
在页面部分添加一个测试方法
<template>
<div>
<h3>3年过去了,多少岁了?</h3>
<div>
<button @click="tellMe">点击我告诉你</button>
</div>
</div>
</template>
payload 参数可以是对象类型,这样可以传递更复杂的参数
methods: {
tellMe() {
this.$store.commit('addAge', { years: 3 })
}
}
如果我们需要操作多个数据的时候,代码也会有点多,和 mapState、mapGetters 类似,mutations 也有映射函数 mapMutations,使用它可以帮助我们简化代码。
mapMutations
直接上代码
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations(['addAge'])
}
}
上述代码相当于下面这段代码
import { mapMutations } from 'vuex'
export default {
methods: {
addAge(payload) {
this.$store.commit('addAge', payload)
}
}
}
然后可以发起调用
methods: {
tellMe() {
this.addAge({ years: 3 })
}
}
甚至你也可以这样调用
<button @click="addAge({ years: 3 })">点击我告诉你</button>
注意点:mutations 不推荐写异步方法,比如 axios、setTimeout 等,mutations主要是的作用是修改 state。
使用常量替代事件类型
查看原来项目中的代码,发现可以把方法名称用常量来代替。这样的话可以很大程度上避免写错,eslint也会提示错误位置。
import Vue from 'vue'
import Vuex from 'vuex'
export const ADD_AGE = 'addAge'
Vue.use(Vuex)
export default new Vuex.Store({
// 存放状态
state: {
age: 51,
name: 'Parent',
nickName: '父组件'
},
getters: {
desc(state) {
return state.name + '的年龄是' + state.age
}
},
mutations: {
[ADD_AGE](state, payload) {
state.age += payload.years
}
},
actions: {
},
modules: {
}
})
把 addAge 方法名定义为一个常量,调用的时候直接引入进来
import { ADD_AGE } from '../store'
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([ADD_AGE])
}
}
通过阅读 H5 的项目代码,发现是新建了一个 types.js
的文件,专门用来存储这些常量,然后在需要使用的 store js 文件里引入, 这样看起来确实统一一些更有利于管理,具体代码这里就不贴了,参见项目。
四、Actions
Action 类似于 mutation,区别在于 Actions 提交的是 mutaion,而不是直接修改状态 state,另外 Actions 可以包含异步操作。
先在 actions 中定义一个方法,并且返回一个对象
actions: {
getUserInfo() {
return {
age: 51,
name: 'Parent',
nickName: '父组件'
}
}
}
然后在 Vue 文件的 created 中调用该方法,并且将结果赋值给 userInfo,并且打印。从控制台来看,是一个 Promise ,这说明 actions 中的方法默认就是异步的。
created() {
var userInfo = this.getUserInfo()
console.log(userInfo)
},
methods: {
...mapActions(['getUserInfo'])
}
Action 通过 store.dispatch 方法触发
this.$store.dispatch('getUserInfo')
而上述代码中的 ...mapActions(['getUserInfo'])
则相当于以下代码
getUserInfo() {
return this.$store.dispatch('getUserInfo')
}
在实际项目中,一般 state 的属性值都是空的,在完成登录操作后才可以获取到用户信息。下面简单看一下模拟的代码:
created() {
this.getUserInfo()
}
methods: {
...mapActions(['getUserInfo'])
}
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 存放状态
state: {
age: 51,
name: 'Parent',
nickName: '父组件'
},
mutations: {
setUserInfo(state, payload) {
state.age = payload.age
state.name = payload.name
state.nickName = payload.nickName
}
},
actions: {
async getUserInfo(context) {
const res = await axios.get('/api/fetchUserInfo')
context.commit('setUserInfo', res)
}
},
modules: {
}
})
简单分析一下,如果要给 state 赋值,肯定会先想到通过 mutations 来操作 state,但是数据是从网络请求接口异步去取得的,所以还不能用 mutations 而是用 actions,也就是说:通过 actions 来操作 mutations,从而再去操作 state。
在运行的过程中,先是调用 getUserInfo 方法,进入了actions,然后通过 commit 调用了 setUserInfo,并且把 res(用户信息)作为参数传进去,最后将对应的属性逐一赋值给 state。
简单总结:Mutations 负责存入,只要分发给我我就存;Actions 负责中间处理,处理完成我就给到 Mutations,Mutations 你要怎么存我不管,所有对 state 状态赋值都是由 Mutations 来操作的;而 Getters 则只负责取,不进行修改。
五、Modules
想象一下,随着项目的发展,store文件的东西越来越多,变得非常臃肿,这种单一状态树的做法变得不太好维护。 Vuex 为了解决上述问题,允许我们将 store 分割成模块(module),每个模块都可以拥有自己的 state、mutation、action 以及 getter,甚至还能嵌套子模块。
const userModule = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const customerModule = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
user: userModule,
customer: customerModule
}
})
store.state.user // userModule的状态
store.state.customer // customerModule的状态