Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
安装Vuex
1、通过脚手架创建项目
通过脚手架创建项目可以勾选Vuex选项,会根据项目使用的是 vue2 还是 vue3 自动安装对应版本的 Vuex。
2、在已有项目上手动安装
在已有项目上通过 npm i 手动安装Vuex,需根据项目使用 vue2 还是 vue3 选择 Vuex 的版本,如果使用的是 vue2 ,则须安装 Vuex3,对应命令 npm i vuex@3 ,如果使用的是 vue3 ,则须安装 Vuex4,对应命令 npm i vuex@4 。
手动安装vuex后须在 src 文件夹下新添 store 文件夹,在 store 文件夹下创建 index.js 文件。
vuex工作原理图
配置vuex
// index.js 改文件用于创建vuex中最为核心的store
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 把vuex 使用use方法 引入vue中
Vue.use(Vuex)
// 可以使用modules把vuex模块化
const modules = {
}
// 准备state--用于存储数据
const state = {
msg:'state初始值,组件共享的数据',
num:0
}
// 准备actions--用于响应组件中的动作
// actions是用来调后台接口的并commit提交一个mutation
const actions = {
}
// 准备mutations--用于操作数据
// 在mutations中修改state中的值(修改state中的值想要在devtools中留下记录必须使用mutations修改)
const mutations = {
ADDNUM:function(state){
state.num++
console.log('ADDNUM',state);
},
DELNUM:function(state){
state.num--
console.log('DELNUM',state);
}
}
// 计算组件中的数据,可以对数据进行二次加工,类似computed功能
const getters = {
}
// 导出一个Vuex.Store的实例化对象
export default new Vuex.Store({
state,
actions,
mutations,
getters,
modules
})
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 引入Vuex.Store的实例化对象store
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
// 全局传入store配置项
router,
render: h => h(App)
}).$mount('#app')
State
state 用于存储所有组件都可以使用的公共数据,当state中的数据发生改变,所有使用这些数据的组件视图也会随之重新渲染。
使用state中的数据
所有组件都可以通过this.$store.state获取state中的数据。
<h1>{{$store.state.msg}}</h1>
<h1>{{$store.state.str}}</h1>
<h1>{{$store.state.num}}</h1>
使用计算属性能让我们更简单地使用state中的数据。
<template>
<h1>{{msg}}</h1>
</template>
<script>
export default {
name:'App',
computed:{
msg(){
return this.$store.state.msg
}
}
}
</script>
使用 mapState 辅助函数生成计算属性
当需要使用state中的多个数据时,无论是使用$store.state直接插入插值表达式,亦或是为每个数据声明一个计算属性,代码都会显得重复与冗余,我们可以使用 mapState 辅助函数帮助我们生成计算属性。
import {mapState} from 'vuex'
export default {
name:'App',
computed:{
// msg(){
// return this.$store.state.msg
// }
// 当映射的计算属性的名称与 state 的子节点名称相同时,我们可以给 `mapState` 传一个字符串数组。
...mapState(['msg','str','num'])
// 当映射的计算属性的名称与 state 的子节点名称不同时,我们需要给 `mapState` 传一个对象。
// getMsg(){
// return this.$store.state.msg
// }
...mapState({getMsg:'msg',getStr:'str',getNum:'num'})
}
}
Getters
有时我们需要对state中数据进行筛选再使用,如果有多个组件需要用到这个筛选的数据,我们可以在getters中定义这个数据。
getters可以认为是store的计算属性,在Vue 2中,getters的结果像计算属性一样会被缓存起来,直到state中对应的数据发生改变。
// Getter 接受 state 作为其第一个参数
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: (state) => {
return state.todos.filter(todo => todo.done)
}
}
})
我们可以通过this.$store.getters,以属性的形式获取getters的结果。在使用时可以通过插值表达式直接引入,也可以声明一个计算属性。
<h1>{{$store.getters.doneTodos}}</h1>
computed: {
doneTodos () {
return this.$store.getters.doneTodos
}
}
使用 mapGetters 辅助函数生成计算属性
同样的,我们可以使用 mapGetters 辅助函数帮助我们生成计算属性。
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
Mutations
想要更改 state 中的数据,我们必须通过commit提交一个mutation,每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler),回调函数接受 state作为第一个参数,也可以传入额外的参数作为第二个参数。
const store = createStore({
state: {
count: 1
},
mutations: {
// 每个mutation的回调函数名一般大写,用以和actions里的回调函数名作区分
INCREMENT (state) {
// 变更状态
state.count++
}
}
})
// 组件内的方法。通过一个事件调用`commit`方法去调用 `mutation` 处理函数
methods:{
add(){
this.$store.commit('INCREMENT')
}
}
// ...
mutations: {
INCREMENT (state, n) {
state.count += n
}
}
// 组件内的方法。通过一个事件调用`commit`方法去调用 `mutation` 处理函数
methods:{
add(){
this.$store.commit('INCREMENT',2)
}
}
我们不能直接调用一个 mutation 处理函数,必须通过调用 store.commit 方法,去调用一个 mutation 处理函数。如果不用通过后台接口传入额外的参数,我们可以通过一个事件调用commit方法去调用 mutation 处理函数。如果需要通过后台接口,则先需要dispatch调用一个action(关于action见下Actions),然后在action的回调函数里调用commit方法去调用 mutation 处理函数。
// 不需要调用后台接口
methods:{
add(){
this.$store.commit('INCREMENT')
}
}
// 需要调用后台接口
methods:{
add(){
this.$store.dispatch('increment')
}
}
const store = createStore({
state: {
count: 1
},
actions:{
increment(context){
context.commit('INCREMENT')
}
},
mutations: {
INCREMENT (state) {
// 变更状态
state.count++
}
}
})
使用mapMutations辅助函数
使用mapMutations辅助函数可以帮我们省略在methods中去定义一个事件调用commit方法。
methods:{
// 数组写法
...mapMutations(['ADDNUM','DELNUM'])
// 对象写法
...mapMutations({add:NUM',del:'DELNUM'})
}
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num:0
},
getters: {
},
mutations: {
ADDNUM(state,value){
state.num += value
},
DELNUM(state,value){
state.num -= value
}
}
})
Actions
因为 mutation 必须同步执行这个限制,所以我们在actions内部执行异步操作:
actions: {
addnum({commit},value){
setTimeout(() => {
commit('ADDNUM',value)
}, 1000);
},
delnum({commit},value){
setTimeout(() => {
commit('DELNUM',value)
}, 1000);
}
}
Action 通过 dispatch 方法触发:
export default {
methods:{
add(){
this.$store.dispatch('addnum',2)
}
}
}
使用mapActions函数
同样的,使用mapActions辅助函数可以帮我们省略在methods中去定义一个事件调用commit方法。
import {mapActions} from 'vuex'
export default {
methods:{
// 数组写法
...mapActions(['addnum','delnum']),
// 对象写法
...mapActions({add:'addnum',del:'delnum'})
}
}
关于通过commit提交mutation的的注意点
vue的devtools工具会同步记录mutation触发时state的状态。如果mutation是异步函数,或者在组件事件中异步调用调用dispatch或commit方法,都可能会造成devtools中记录的数据混乱。
1、mutation必须是同步函数
如果mutatiopn是异步函数,当mutation触发时回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。
const store = createStore({
state: {
count: 1
},
mutations: {
INCREMENT (state) {
setTimeout(()=>{
state.count++
},500)
}
}
})
2、在组件的事件中必须同步调用dispatch或commit
在组件中异步调用dispatch或commit方法同样会造成devtools中记录的数据混乱。
下图在一秒内触发了三次 increment 回调函数 异步,一秒后调用 dispatch 方法的回调函数才触发继而调用commit方法触发mutation,此时this.num的值为6,因此devtools的三条记录中state.num分别是6、12、18。
Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
// modeA.js
const state = ()=>({
modeAMsg:'模块A的msg'
})
const getters = {
getMsgOfA:(state)=> state.modeAMsg+'--getters'
}
const mutations = {
CHANGEMSGOFA(state,value){
state.modeAMsg = value
}
}
const actions = {
changemsgAwait({commit},value){
setTimeout(() => {
commit('CHANGEMSGOFA',value)
}, 3000);
}
}
export default {
// 如果希望你的模块具有更高的封装度和复用性,你可以通过添加 `namespaced: true` 的方式使其成为带命名空间的模块。
namespaced:true,
state,
getters,
mutations,
actions
}
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import modeA from '@/store/modules/modeA'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
modeA
}
})
在组件中使用modeA
通过this.$store.state.modeA.modeAMsg可以访问modeA中的modeAMsg。
通过this.$store.getters['modeA/getMsgOfA']可以访问modeA中的getMsgOfA。
调用commit方法调用modeA中的mutation。
changeFn1(){
this.$store.commit('modeA/CHANGEMSGOFA',prompt('请输入改变后的值'))
},
调用dispatch方法调用modeA中的action。
changeFn2(){
this.$store.dispatch('modeA/changemsgAwait',prompt('请输入改变后的值'))
},
使用辅助函数
当使用 mapState、mapGetters、mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐,可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
computed:{
...mapState('modeA',['modeAMsg']),
...mapGetters('modeA',['getMsgOfA'])
},
methods:{
...mapMutations('modeA',['CHANGEMSGOFA']),
...mapActions('modeA',['changemsgAwait'])
}
}
一个结合以上两种用法的小demo。
<template>
<div id="app">
<h1>App</h1>
<h2>Modules</h2>
<h3>模块A的msg:{{$store.state.modeA.modeAMsg}}</h3>
<h3>模块A的msg(mapState):{{modeAMsg}}</h3>
<h3>模块A的getMsgOfA:{{$store.getters['modeA/getMsgOfA']}}</h3>
<h3>模块A的getMsgOfA(mapGetters):{{getMsgOfA}}</h3>
<P>
<button @click="changeFn1">改变模块A的msg</button> |
<button @click="changeFn2">异步改变模块A的msg</button> |
<button @click="changeFn3">改变模块A的msg(mapMutations)</button> |
<button @click="changeFn4">异步改变模块A的msg(mapActions)</button>
</P>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name: 'App',
data(){
return {
count:0
}
},
created(){
console.log(this);
},
computed:{
...mapState(['num']),
...mapState('modeA',['modeAMsg']),
...mapGetters('modeA',['getMsgOfA'])
},
methods:{
...mapMutations(['ADDNUM','DELNUM']),
...mapActions(['addnum','delnum']),
...mapMutations('modeA',['CHANGEMSGOFA']),
...mapActions('modeA',['changemsgAwait']),
changeFn1(){
this.$store.commit('modeA/CHANGEMSGOFA',prompt('请输入改变后的值'))
},
changeFn2(){
this.$store.dispatch('modeA/changemsgAwait',prompt('请输入改变后的值'))
},
changeFn3(){
this.CHANGEMSGOFA(prompt('请输入改变后的值'))
},
changeFn4(){
this.changemsgAwait(prompt('请输入改变后的值'))
},
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>