其他章节请看:

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 快速入门 系列

Vuex 基础的更多相关文章

  1. vuex 基础:教程和说明

    作者注:[2016.11 更新]这篇文章是基于一个非常旧的 vuex api 版本而写的,代码来自于2015年12月.但是,它仍能针对下面几个问题深入探讨: vuex 为什么重要 vuex 如何工作 ...

  2. Vuex基础-Module

    官方API地址:https://vuex.vuejs.org/zh/guide/modules.html 前面几节课写的user.js就称为一个module,这样做的原因是:由于使用单一状态树,应用的 ...

  3. Vuex基础-Action

    在文章开始之前,再次强调一句:Vuex会把getter mutations action不管是在模块定义的还是在根级别定义的 都会注册在全局 官网API地址:https://vuex.vuejs.or ...

  4. Vuex基础-Mutation

    借助官网的一张图,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation.不可以直接对其进行赋值改变.需要注意的是,mutations只能做一些同步的操作. ​​​ 代码结构: ​ ...

  5. Vuex基础-Getter

    官方地址:https://vuex.vuejs.org/zh/guide/getters.html Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性).就像 ...

  6. Vuex基础-State

    官方地址:https://vuex.vuejs.org/zh/guide/state.html 由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个 ...

  7. 前端vuex基础入门

    vuex简介 是一个专门为vue.应用程序开的状态管理模式 它采用集中式存储管理应用的所有组件的状态 (类似于全局变量) 并以相应的规则保证以一种可预测的方式发生改变(相应式变化) 应用场景 多个视图 ...

  8. vuex基础入门

    Vuex简介 vuex的安装和组成介绍 [外链图片转存失败(img-nWQUUuyh-1565273314232)(https://upload-images.jianshu.io/upload_im ...

  9. Vuex基础 -01 -实现简易计数器 -支持 加数/ 减数/ 奇数再加/ 异步加法(setTimeout 1000ms) -单组件演示语法

    Vuex 的结构图 工程组织 Vuex的核心管理程序 store.js /* vuex的核心管理程序 */ import Vue from 'vue' import Vuex from 'vuex' ...

随机推荐

  1. String转double失去精度问题

    最近遇到一个坑,微信小程序中退款 19.9的字符串转double变成19.89,导致退不成功 . 坑死我了.现在把更改后的代码贴出来 public static void main(String[] ...

  2. Mysql时间戳转Java时间戳

    MySQL 时间戳和Java返回的时间戳是不一样的 例如: 当前时间是 2014-08-04 10:42:55.204000 使用mysql时间戳函数UNIX_TIMESTAMP 返回的结果为: 14 ...

  3. 一、Rabbitmq的简单介绍

    以下只是本人从零学习过程的整理 部分内容参考地址:https://www.cnblogs.com/ysocean/p/9240877.html 1.RabbitMQ的概念 RabbitMQ是实现了高级 ...

  4. python-request 实现企业微信接口自动化-1(DDT)

    环境准备 python+requests 读取企业微信api开发文档,得知调用企业微信接口必须先获取企业微信的accesstoken是通过 ("corpid","&quo ...

  5. Python - poetry(3)配置项详解

    config 命令 poetry 通过 config 命令进行配置 也可以直接在 config.toml 文件中进行配置,该文件将在首次运行该命令时自动创建 文件目录 macOS:~/Library/ ...

  6. Java优化if-else代码

    前言 开发系统一些状态,比如订单状态:数据库存储是数字或字母,但是需要显示中文或英文,一般用到if-else代码判断,但这种判断可读性比较差,也会影响后期维护,也比较容易出现bug.比如: 假设状态对 ...

  7. oracle table()函数

    PL/SQL表---table()函数用法/* PL/SQL表---table()函数用法:利用table()函数,我们可以将PL/SQL返回的结果集代替table. oracle内存表在查询和报表的 ...

  8. ssh跳转设置

    SSH工具的非常规使用 原创 景朝阳 FreeSWITCH中文社区 今天   说明:本文所有的命令是基于OpenSSH客户端7.4p1版本.如果出现命令不正确,请检测是否为此OpenSSH版本. 设置 ...

  9. 导出excel、word、csv文件方法汇总

    http://www.woaic.com/2012/06/64 excel文件主要是输出html代码.以xls的文本格式保存文件. 生成excel格式的代码: /// <summary> ...

  10. 使用zipKin构建NetCore分布式链路跟踪

    本文主要讲解使用ZipKin构建NetCore分布式链路跟踪 场景 因为最近公司业务量增加,而项目也需要增大部署数量,K8S中Pod基本都扩容了一倍,新增了若干物理机,部分物理机网络通信存在问题,导致 ...