其他章节请看:

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. Ubuntu 系统安装、配置

    windows下制作安装U盘 使用工具:Universal USB Installer ubuntu下制作安装U盘 使用工具:Startup Disk Creator(自带) 选择国内源:Switch ...

  2. Linux centos7 pstree

    2021-08-12 1.命令简介pstree (display a tree of processes) 命令用于查看进程树之间的关系,即哪个进程是父进程,哪个是子进程,可以直观地看出是谁创建了谁. ...

  3. C# 实现图片上传

    C# 实现图片上传 C#实现图片上传: 通过页面form表单提交数据到动作方法,动作方法实现保存图片到指定路径,并修改其文件名为时间格式 页面设置 这里使用的模板MVC自带的模板视图 <h2&g ...

  4. Python 高级特性(3)- 列表生成式

    range() 函数 日常工作中,range() 应该非常熟悉了,它可以生成一个迭代对象,然后可以使用 list() 将它转成一个 list # 判断是不是迭代对象 print(isinstance( ...

  5. java多线程 synchronized 与lock锁 实现线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 通过卖火车票的例子 火车站要卖票,我们模 ...

  6. ☕【Java技术指南】「并发编程专题」CompletionService框架基本使用和原理探究(基础篇)

    前提概要 在开发过程中在使用多线程进行并行处理一些事情的时候,大部分场景在处理多线程并行执行任务的时候,可以通过List添加Future来获取执行结果,有时候我们是不需要获取任务的执行结果的,方便后面 ...

  7. 案例九:shell脚本自动创建多个新用户,并设置密码

    此脚本是用来批量创建用户并设置用户密码,在企业用非常实用. 脚本一 #!/bin/bash for name in $( seq 1 100 ) do useradd "user$name& ...

  8. 手机UI自动化之显示点触位置(触摸轨迹)

    上期回顾:Airtest源码分析--Android屏幕截图方式 不管是用Appium还是Airtest,或是其他手机UI自动化工具,你是不是经常遇到这种情况,代码明明执行了click或swipe,怎么 ...

  9. Cookie和Session的介绍与认识

    Cookie: cookie是一种客户端的状态管理技术. 当浏览器向服务器发送请求的时候,服务器会将少量的数据以set-cookie消息头的方式发送给浏览器,当浏览器再次访问服务器时,会将这些数据以c ...

  10. 动态查看及加载PHP扩展

    在编译并完成 php.ini 的配置之后,我们就成功的安装了一个 PHP 的扩展.不过, PHP 也为我们提供了两个在动态运行期间可以查看扩展状态以及加载未在 php.ini 中进行配置的扩展的函数. ...