版本问题
截止博客发布前,Vuex 拥有 3.x 和 4.x 版本。
本文总结的是与 Vue 2 匹配的 Vuex 3 的使用。
更多操作请参考官方文档:https://v3.vuex.vuejs.org/
与 Vue 3 匹配的 Vuex 4 的文档: https://vuex.vuejs.org/
状态管理模式
写过 vue 项目的程序员应该都知道父子组件传参的方式,通过子组件的 props 参数,和 $emit 来触发事件。
但是当遇到多个组件共享状态时,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力,甚至会导致无法维护的代码。
基于此,把组件的共享状态抽取出来,以一个全局单例模式管理,vuex 就产生了。
安装使用
Vue 2 的脚手架项目,安装 3.6.2版本。
npm install vuex@3.6.2 --save
在 src 目录下新建 store 文件夹,在该目录下新建 index.js
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
count: 1
}
const getters = {
}
const mutations = {
add(state, n) {
state.count += n
},
reduce(state) {
state.count--
}
}
const actions = {
}
export default new Vuex.Store({
state,
getters,
mutations,
actions
})
入口文件引入
main.js
import store from './store'
new Vue({
el: '#app',
store,
components: {
App
},
template: '<App/>'
})
简单示例
通过 commit 提交 mutation 中的方法修改 state。
组件代码
<template>
<div>
<el-button @click="$store.commit('add', 1)">add</el-button>
<el-button @click="$store.commit('reduce')">reduce</el-button>
<div>值:{{ $store.state.count }}</div>
</div>
</template>
在实际开发过程中,直接使用 $store. 这样的方式似乎不太优雅,可以参考以下写法。
<script>
import { mapState, mapMutations } from "vuex";
export default {
name: "vuex",
data() {
return {};
},
computed: {
...mapState(["count"])
},
mounted() {
},
methods: {
...mapMutations(["add", "reduce"])
}
};
</script>
方法调用
<template>
<div>
<el-button @click="add(1)">add</el-button>
<el-button @click="reduce">reduce</el-button>
<div>值:{{ count }}</div>
</div>
</template>
五大核心概念
1、State
相当于 Vue 中的 data,以及 React 中的 state。
Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态,单状态树和模块化并不冲突。
获取 state
如上述示例所示,html 中可以使用 $store.state.count
获取(属性访问),js 中需要在前面加 this.
还可以通过 mapState
辅助函数,将状态加入计算属性中。
2、Getters
getter可以认为是 store 的计算属性,返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
如果有多个组件需要共用状态的某计算属性,可以使用它。
修改上述 index.js
const state = {
count: 1,
todos: [{
id: 1,
text: '...',
done: true
},
{
id: 2,
text: '...',
done: false
}
]
}
const getters = {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
mapGetters 辅助函数
import { mapGetters, mapMutations } from "vuex";
computed: {
...mapGetters(["doneTodos"])
},
mounted() {
console.log(this.doneTodos); //
},
3、Mutations
更改状态的唯一方法是提交 mutation。
如上示例,mutation 中可以定义更改状态 state 的方法。也可以使用常量替代 Mutation 事件类型。
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
const mutations = {
// 我们可以使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
注意:一条重要的原则就是要记住 mutation 必须是同步函数。
在组件中提交 mutation 主要用到 commit
方法,或者 mapMutations
辅助函数,参考上述示例。
4、Actions
Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
也就是说,如果有需要进行后台接口更新操作,需要使用 Action。
修改上述 index.js
const actions = {
addAsync({ commit }) {
setTimeout(() => {
commit('add', 10)
}, 1000);
}
}
在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数,调用类似 mapMutations
。
5、Modules
module,即模块。Vuex 允许多模块以应对复杂的应用而造成 store 对象臃肿的问题。
为了方便管理,在 store 下新建 modules 目录,然后在该目录下新建一个 dept.js。
dept.js
const state = {
deptList: [{
deptID: 0,
deptName: '总经理室'
}]
}
const getters = {}
const mutations = {
add(state, dept) {
state.deptList.push(dept)
}
}
const actions = {}
export default {
namespaced: true, // 使用命名空间
state,
actions,
getters,
mutations
}
然后修改 store 目录下的 index.js,引入模块
const modules = {}
const require_module = require.context('./modules', false, /.js$/)
require_module.keys().forEach(file_name => {
console.log(file_name)
modules[file_name.slice(2, -3)] = require_module(file_name).default
})
export default new Vuex.Store({
modules,
state,
getters,
mutations,
actions
})
组件调用 state
<div>{{ $store.state.dept.deptList }}</div>
// 或
<div>{{ dept.deptList }}</div>
computed: {
...mapState(["dept"])
},
调用 mutation
this.$store.commit("dept/add", { deptName: "开发部", deptID: 1 });
// 或
<el-button @click="add({deptName: '开发部'})">add</el-button>
methods: {
...mapMutations("dept", ["add"])
}