对于组件化开发来说,大型应用的状态往往跨越多个组件。在多层嵌套的父子组件之间传递状态十分麻烦,而Vue更是没有为兄弟组件提供直接共享数据的方法。
基于这个问题,许多框架提供了解决方案:使用全局的状态管理器,将所有分散的共享数据交由状态管理器保管,Vue也不例外。
VueX 是专为Vue.js 应用程序开发的状态管理库,采用集中式存储来管理应用的所有组件状态。
VueX 用于管理分散在Vue各个组件中的数据。兄弟组件间的数据传递。
vuex有两个版本,分别vuex3对应vue2 ; vuex4对应vue3
两种版本安装方式:
vuex3安装:npm install vuex@3
vuex4安装:npm install vuex
官方网站:
https://v3.vuex.vuejs.org/zh/
关于状态管理:
一般在复杂应用中才会用到VueX ,并非必须的。
每一个VueX应用的核心都是一个store (仓库)全局对象,与普通的全局对象不同,基于Vue数据与视图绑定的特点,当store中的状态(数据)发生变化时,与之绑定的视图也会被重新渲染。
store中的状态不允许被直接修改,改变store中的状态的唯一途径就是显式提交(commit)mutation,这可以方便地跟踪每一个状态的变化。
在大型复杂应用中,如果无法有效跟踪到状态的变化,将会对理解和维护代码带来极大困扰
VueX中有5个重要概念:State、Getter、Mutation、Action、Module
下面进行演示
创建一个基于 Vue2 的新项目:具体创建方式详见笔记(九)
vue create vuex-demo
将刚刚创建的项目 vuex-demo 拖入 Visual Studio Code 中
vuex3安装:npm install vuex@3
在src目录下新建一个 store 文件夹,新建一个 index.js 文件,提供一个初始 state 对象和一些 mutation,文件代码如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
说明:建立了一个store(仓库),默认count初始值为: 0 ;以后state 是状态就是给各个组件调用的。
不能直接改state的状态,如需要修改要调用 mutations 方法(该方法是用于 +1 操作)。
然后将store 导出,这样就可以在main.js中使用了
export default store
现在希望在组件中调用 count 的值,具体操作如下:
1.由于我们是在main.js创建Vue的,因此在main.js 中就可以导入刚刚创建的 index.js
import store from './store/index.js'
由于是以index.js作为文件名,也可以简写省略
import store from './store'
然后用Vuex 提供的一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制:
new Vue({
render: h => h(App),
store:store
}).$mount('#app')
做完这些就可以在任意组件中利用store对象去取值。
2.新建一个Test.vue 组件,代码如下:
<template>
<div>
</div>
</template>
<script>
export default {
name:'Test'
}
</script>
在根组件App.vue中进行注册
在这个Test.vue 中将 count 的值取出来。
{{ this.$store.state.count }}
运行项目后,浏览器就可以显示出 count 的初始值 :“0”。这个时候Test.vue 组件就成功调用了全局状态值。
如何修改状态码?增加一个按钮,进行加1操作。
<button @click="add">+1</button>
定义add方法:
methods:{
add(){
this.$store.commit("increment")
}
}
再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。
这里调用的是index.js 中 store里面的 increment 方法来进行修改操作,而不是用 this. s t o r e . s t a t e . c o u n t = t h i s . store.state.count = this. store.state.count=this.store.state.count +1
在浏览器中点击 +1 按钮就可以改变状态了
优化1:可以通过设置一个方法,然后就可以直接调用 {{ count }}
computed: {
count () {
return this.$store.state.count
}
},
优化2:mapState 辅助函数生成计算属性
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键。
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
Getter
有时候我们需要从 store 中的 state 中派生出一些状态,例如对state 数据进行过滤并进行计数等处理。
下面进行演示,在state 中再增加一个 todos 待办事项列表。其中,吃饭已经完成;睡觉尚未完成。
todos: [
{ id: 1, text: '吃饭', done: true },
{ id: 2, text: '.睡觉', done: false }
]
假设要显示到 Test.vue 组件上,现将 todos 映射过来,然后就可以使用。
现在假设只是想显示已经完成的动作,该怎样操作?
这就需要对 todos 进行过滤。在 index.js 中增加一个 getters 方法(注意:两个方法之间要加逗号 “ , ”)
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
然后就可以在Test.vue 组件中使用 mapGetters 辅助函数来映射。
由于我们原来已经使用了 mapState ,现在又要同时用另外一个方法 mapGetters 就需要对代码进行一些修改:
computed:{
...mapState([
'count','todos'
]),
...mapGetters([
'doneTodos'
])
},
这时浏览器就过滤出 done 状态为 true 的数据
下面的Mutation、Action、Module 后面的实例讲解中才能更好理解,在此仅作为了解。
- Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
组件中提交 Mutation的方法,代码如下。不再此进行演示,有兴趣可以自己试一下。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}}
- Action
在 mutation 中处理混合异步调用时就要用到 Action 。(在 Vuex 中,mutation 都是同步事务:)
先注册一个简单的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}})
在组件中分发 Action ,与前面类似:methods 中使用 mapActions
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}}
- Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
最终所有模块都要和到一起。当我们创建 Vuex.Store 时,通过 modules 参数给模块起名字,比如:给 moduleA 取名为 a 。
需要访问的模块的时候就加上 a 。store.state.a // -> moduleA 的状态。
其实就是对模块进行分割管理,比如:用户模块的状态,订单模块的状态等分别设置。
上述仅为 Vuex 的基本概念,具体使用到后面的综合案例再具体演示。