其他章节请看:

vue 快速入门 系列

Vuex 基础

Vuex 是 Vue.js 官方的状态管理器

vue 的基础应用(上)一文中,我们已知道父子之间通信可以使用 props$emit,而非父子组件通信(兄弟、跨级组件、没有关系的组件)使用 bus(中央事件总线)来起到通信的作用。而 Vuex 作为 vue 的一个插件,解决的问题与 bus 类似。bus 只是一个简单的组件,功能也相对简单,而 Vuex 更强大,使用起来也复杂一些。

现在的感觉就是 Vuex 是一个比 bus 更厉害的东西,可以解决组件之间的通信。更具体些,就是 vuex 能解决多个组件共享状态的需求:

  • 多个视图(组件)依赖于同一状态
  • 来自不同视图(组件)的行为需要变更同一状态。

Vuex 把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。

环境准备

通过 vue-cli 创建项目

// 项目预设 `[Vue 2] less`, `babel`, `router`, `vuex`, `eslint`
$ vue create test-vuex

Tip:环境与Vue-Router 基础相同

核心概念

Vuex 的核心概念有 State、Getters、Mutations、Actions和Modules。

我们先看一下项目 test-vuex 中的 Vuex 代码:

// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({
// vuex 中的数据
state: {
},
// 更改 vuex 中 state(数据)的唯一方式
mutations: {
},
// 类似 mutation,但不能直接修改 state
actions: {
},
// Vuex 允许将 store 分割成模块(module),每个模块可以拥有自己的 state、mutation、action、getter
modules: {
}
})

Getters,可以认为是 store 的计算属性

State

state 是 Vuex 中的数据,类似 vue 中的 data。

需求:在 state 中定义一个属性 isLogin,从 About.vue 中读取该属性。

直接上代码:

// store/index.js
export default new Vuex.Store({
state: {
isLogin: true
},
})
// views/About.vue
<template>
<div class="about">
<p>{{ this.$store.state.isLogin }}</p>
</div>
</template>

页面输出 true

Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)),子组件能通过 this.$store 访问,这样就无需在每个使用 state 的组件中频繁的导入。

// main.js
new Vue({
store,
render: h => h(App)
}).$mount('#app')
// store/index.js
Vue.use(Vuex)

Tip:Vuex 的状态存储是响应式。

mapState 辅助函数

从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键。

// views/About.vue
<template>
<div class="about">
<p>{{ isLogin }}</p>
</div>
</template>
<script>
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex' export default {
computed: mapState([
// 映射 this.isLogin 为 store.state.isLogin
'isLogin'
])
}
</script>

页面同样输出 true。

Tip:更多特性请看官网

Getters

Getters,可以认为是 store 的计算属性。

getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

需求:从 isLogin 派生出一个变量,从 About.vue 中读取该属性

直接上代码:

// store/index.js
export default new Vuex.Store({
state: {
isLogin: true
},
getters: {
translationIsLogin: state => {
return state.isLogin ? '已登录' : '未登录'
}
},
})
// views/About.vue
<template>
<div class="about">
<p>{{ this.$store.getters.translationIsLogin }}</p>
</div>
</template>

页面输出“已登录”

Tip:更多特性请参考官网。

  • 可以给 getter 传参
  • 有与 state 类似的辅助函数,这里是 mapGetters

Mutations

mutation 是更改 vuex 中 state(数据)的唯一方式。

mutation 类似事件,每个 mutation 都有一个字符串的事件类型和 一个回调函数。不能直接调用一个 mutation handler,只能通过 store.commit 方法调用。

需求:定义一个 mutation(更改 isLogin 状态),在 About.vue 中过三秒触发这个 mutation。

直接上代码:

// store/index.js
export default new Vuex.Store({
state: {
isLogin: true
},
mutations: {
toggerIsLogin(state) {
state.isLogin = !state.isLogin
}
},
})
// views/About.vue
<template>
<div class="about">
<p>{{ isLogin }}</p>
</div>
</template>
<script>
export default {
created() {
setInterval(()=>{
this.$store.commit('toggerIsLogin')
}, 3000)
},
}
</script>

页面每三秒会依次显示 true -> false -> true ...

Mutation 必须是同步函数
  • 笔者在 mutation 中写异步函数(使用 setTimeout)测试,没有报错
  • 在 mutation 中混合异步调用会导致程序很难调试(使用 devtools)
  • 当调用了两个包含异步回调的 mutation 来改变状态,不知道什么时候回调和哪个先回调

结论:在 mutation 中只使用同步函数,异步操作放在 action 中。

Tip:更多特性请参考官网。

  • 可以给 mutation 传参
  • 触发(commit)方式可以使用对象
  • 有与 state 类似的辅助函数,这里是 mapMutations

Actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

需求:定义一个 action,里面有个异步操作,过三秒更改 isLogin 状态。

直接上代码:

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({
state: {
isLogin: true
},
mutations: {
toggerIsLogin(state) {
state.isLogin = !state.isLogin
}
},
actions: {
toggerIsLogin(context) {
setInterval(() => {
context.commit('toggerIsLogin')
}, 3000)
}
},
})
// views/About.vue
<template>
<div class="about">
<p>{{ isLogin }}</p>
</div>
</template>
<script>
export default {
created() {
// 通过 dispatch 分发
this.$store.dispatch('toggerIsLogin')
},
}
</script>

过三秒,页面的 true 变成 false。

实践中,我们会经常用到 ES2015 的参数解构来简化代码:

actions: {
toggerIsLogin({ commit }) {
setInterval(() => {
commit('toggerIsLogin')
}, 3000)
}
},

Tip:更多特性请参考官网。

  • 可以给 Actions 传参
  • 触发(dispatch)方式可以使用对象
  • 有与 state 类似的辅助函数,这里是 mapActions
  • 组合多个 Action

Modules

目前我们的 store 都写在一个文件中,当应用变得复杂时,store 对象就有可能变得相当臃肿。

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})

Vuex 允许将 store 分割成模块(module),每个模块可以拥有自己的 state、mutation、action、getter。

需求:定义两个模块,每个模块定义一个状态,在 About.vue 中显示这两个状态

直接上代码:

// store/index.js
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex) const moduleA = {
state: () => ({ name: 'apple' }),
} const moduleB = {
state: () => ({ name: 'orange' }),
} export default new Vuex.Store({
modules: {
a: moduleA,
b: moduleB,
}
})
// views/About.vue
<template>
<div class="about">
<!-- 即使给这两个模块都加上命名空间,这样写也是没问题的 -->
<p>{{ this.$store.state.a.name }} {{ this.$store.state.b.name }}</p>
</div>
</template>

页面显示 “apple orange”。

模块的局部状态

对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。就像这样:

const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
}

对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState。就像这样:

const moduleA = {
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的。

如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。请看示意代码:

const store = new Vuex.Store({
modules: {
account: {
namespaced: true, // 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
}, // 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
}, // 进一步嵌套命名空间
posts: {
namespaced: true, state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})

小练习

请问 About.vue 会输出什么?(答案在文章底部)

// views/About.vue
<template>
<div class="about">
<p>{{ this.$store.state.a.name }} {{ this.$store.state.b.name }}</p>
<p>
{{ this.$store.getters.nameA }} {{ this.$store.getters.nameB }}
{{ this.$store.getters["b/nameB"] }}
</p>
</div>
</template>
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex) const moduleA = {
namespaced: true,
state: () => ({ name: 'apple' }),
} const moduleB = {
namespaced: true,
state: () => ({ name: 'orange' }),
getters: {
nameB: state => `[${state.name}]`
}
} export default new Vuex.Store({
modules: {
a: moduleA,
b: moduleB,
},
getters: {
nameA: state => state.a.name,
nameB: state => state.b.name
}
})

Tip: 更多特性请参考官网。

项目结构

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,官网给出了一个项目结构示例:

├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块

Tip:在笔者即将完成的另一篇文章“使用 vue-cli 3 搭建一个项目”中会有更详细的介绍

附录

小练习答案

apple orange

apple orange [orange]

其他章节请看:

vue 快速入门 系列

最新文章

  1. 使用的 SQL Server 版本不支持数据类型“datetime2”.
  2. SAP 默认的连接端口
  3. DIV的垂直居中
  4. Block深入浅出
  5. 正则表达式的先行断言(lookahead)和后行断言(lookbehind)
  6. win7下:MySQL-Front的下载与安装
  7. JAVA爬虫代码
  8. 2 将mybatis配置到springmvc中
  9. 2.4 PCI总线的配置
  10. 深悉正则(pcre)最大回溯/递归限制
  11. 体验ToLua框架下热更新(Phpstudy)
  12. EEG 睡眠 节律 代码
  13. 4、hello world
  14. 精读JavaScript模式(八),JS类式继承
  15. 【学习总结】Git学习-参考廖雪峰老师教程八-使用GitHub
  16. Linux操作oracle——关闭、停止、重启
  17. Clion pycharm激活码(可使用到2019年2月)
  18. FullCalendar – jQuery Event Calendar in ASP.NET
  19. Tarjan 强连通分量 及 双联通分量(求割点,割边)
  20. C++客户端访问Java服务端发布的SOAP模式的WebService接口

热门文章

  1. java 查询当天0点0分0秒
  2. 从kratos分析breaker熔断器源码实现
  3. jvm学习笔记:虚拟机栈
  4. IIS托管Asp.net Core及Abp VNext
  5. Request 根据用户输入的信息获取输入到控制台
  6. django报错外理收集
  7. 有关类朋友圈设计(3) -- 数据库设计&amp;现有技术&amp;流程设计
  8. java web利用jsp完成简单的学生管理系统
  9. java线程day-02
  10. PHP中一个好玩的性别判断扩展