Vuex 概念回顾
什么是 Vuex
- Vuex 是专门为 Vue.js 设计的状态管理库(JS库)
- Vuex 采用集中式的方式存储需要共享的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- 相比简单 store 模式 在状态过多时不宜管理
- Vuex 提供了一种模块的机制,可以按模块划分不同功能的状态
- Vuex 的作用是进行状态管理,解决复杂组件通信,数据共享
- Vuex 集成到了Vue的官方调试工具 devtools extension 中,提供了 time-travel 时光旅行和历史回滚功能等功能
什么情况下使用 Vuex
- 非必要的情况不要使用 Vuex
- 如果项目不大,并且组件间状态共享不多的情况下,使用 Vuex 的益处并没有付出的时间多
- 此时使用简单的 store 模式,或其他方式就可以满足需求
- 建议大型的单页应用程序中使用 Vuex,可以更好的管理组件间共享的状态
- 多个视图依赖同一状态
- 来自不同试图的行为需要变更同一状态
官方文档:
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
Vuex 核心概念

流程:
- state - 管理的全局状态
- Vue Components - 把状态绑定(渲染 Render)到组件(视图)展示给用户
- 用户通过 Dispatch 分发 Action
- 可以执行异步请求 Backend API
- 请求完 Commit(提交)Mutation,改变状态的更改并记录
核心概念:
- Store - 仓库
- Store 是使用Vuex应用程序的核心
- 每个应用仅有1个 Store
- Store 是一个容器,包含应用中的大部分状态
- 不能直接改变 Store 中的状态,要通过提交 Mutation 的方式改变状态
- State - 状态
- 状态保存在 Store 中,并且是响应式的
- 因为 Store 是唯一的,所以状态也是唯一的,称为单一状态树
- 但是所有的状态都保存在 State 中的话,会让程序难以维护,可以通 过Module (模块)解决该问题
- Getter - 类似计算属性
- 方便从一个属性派生其他的值
- 内部可以对计算的结果进行缓存
- 只有当依赖的状态发生改变的时候,才会进行计算
- Mutation - 通过提交 Mutation 改变状态
- Mutations 是同步的
- 所有状态的变化必须要通过提交 Mutation 来完成,目的是:
- 可以通过 Mutations 追踪到数据的变化
- 阅读代码是,更容易分析应用内部的状态改变
- 还可以记录每次状态的改变,实现高级调试功能,例如:time travel 和 历史回滚
- Action - 和 Mutation 类似,也是用于改变状态
- 不同的是 Action 可以进行异步的操作
- 内部改变状态的时候,还是要提交 Mutation
- Module - 模块
- 由于使用单一状态树,应用的所有状态会集中到一个比较大的状态上来
- 当应用变得复杂时,Store 对象就有可能变得相当臃肿
- 为了解决以上问题,Vuex 允许将 Store 分割成模块
- 每个模块拥有自己的 State Mutation Action Getter,甚至是嵌套的子模块
Vuex 基本结构
使用Vue CLI 创建项目的时候,如果选择了 Vuex,会自动生成 Vuex 代码的基本结构。
// src/store/index.js
import Vue from 'vue'
// 导入 Vuex
import Vuex from 'vuex'
// 注册插件
Vue.use(Vuex)
// 创建并导出 Vuex中的 Store 对象并导出
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
// getters
})
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 导入 store 对象
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
// 创建 Vue 实例时传入 store 选项
// store 选项会被注入到 Vue 实例中:this.$store
store,
render: h => h(App)
}).$mount('#app')
State
State 是单一状态树,用一个对象存储了全部的应用层级状态(所有的状态数据)。
并且 State 是响应式的。
在仓库(Store)中设置的状态都可以在组件中直接使用,获取数据时,直接从 State 中获取。
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
mutations: {
},
actions: {
},
modules: {
}
})
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
count:{{$store.state.count}}<br/>
msg:{{$store.state.msg}}
</div>
</template>
Vuex 内部提供了 mapState 函数,自动生成状态对应的计算属性。
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
<!-- count:{{ $store.state.count }}<br />
msg:{{ $store.state.msg }} -->
count:{{ count }}<br />
msg:{{ msg }}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 接收一个数组作为参数
// 数组中存放要映射的属性名
// 返回一个包含两个计算属性方法的对象
// return {
// count: state => state.count,
// msg: state => state.msh
// }
...mapState(['count', 'msg'])
}
}
</script>
<style></style>
- 使用 mapState 可以让视图中的代码更简洁。
- 但是如果组件中已经有 count 或 msg 属性,再使用 mapState 就会造成命名冲突。
mapState 还可以接收对象作为参数,使用对象方式可以修改生成的计算属性的名称(解决命名冲突):
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
count:{{ num }}<br />
msg:{{ message }}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 接收一个对象作为参数
// 可以修改生成的计算属性的名称
// key 为最终生成的计算属性的名称
// value 是映射的属性的名称
...mapState({
num: 'count',
message: 'msg'
})
}
}
</script>
<style></style>
Getter
Vuex 中的 Getter 类似计算属性,可以对 state 中的数据进行处理再展示。
- 接收 state 作为参数
- 与计算属性一样,最终返回属性处理后的值
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
},
actions: {
},
modules: {
}
})
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
count:{{ num }}<br />
msg:{{ message }}
<h2>Getter</h2>
reverseMsg:{{$store.getters.reverseMsg}}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
num: 'count',
message: 'msg'
})
}
}
</script>
它同样有简化的函数:mapGetters,使用和 mapState 类似。
- 它用于把 Vuex 中的 getter 映射到组件中的计算属性。
- 同样接收数组和对象,使用和 mapState 一样
- 同样返回一个数组
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
count:{{ num }}<br />
msg:{{ message }}
<h2>Getter</h2>
<!-- reverseMsg:{{$store.getters.reverseMsg}} -->
reverseMsg:{{reverseMsg}}
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState({
num: 'count',
message: 'msg'
}),
...mapGetters(['reverseMsg'])
}
}
</script>
Mutation
Vuex 约定,更改 store 中状态的唯一方法是提交 Mutation,目的是方便在 devtools 中调试。
-
Mutation 必须是同步执行的,这样可以保证能够在 Mutation 中收集到所有的状态修改。
-
不要在 Mutation 中执行异步操作修改 State,否则调试工具无法正常的观测到数据的变化。
-
如果想要执行异步的操作,需要使用 Action。
Vuex 中的mutation非常类似于事件:每个mutation都有一个字符串的 事件类型(type) 和一个 回调函数(handler)。
这个回调函数就是我们实际进行状态更改的地方,它接收两个参数:
- state - 状态
- payload - 载荷,是调用 Mutation 时传递的额外参数
- 多个参数可以包装到一个对象中传递
调用 Store 中的 Mutation 类似于事件,它需要通过 $store.commit 提交,它接收:
- Muataion 的名字作为第一个参数,相当于事件的名称
- 还可以接收第二个参数用于传递额外的参数,即 Mutation 的载荷 payload
使用 mutation 改变状态的好处是:集中的一个位置对状态修改,不管在什么地方修改,都可以追踪到状态的修改。可以实现高级的 time travel 调试功能。
可以通过 mapMutations 把 Mutation 映射到组件的 methods 中,mapMutations 返回的方法封装了 commit 的调用。
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {},
modules: {}
})
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
count:{{ num }}<br />
msg:{{ message }}
<h2>Getter</h2>
reverseMsg:{{reverseMsg}}
<h2>Mutation</h2>
<!-- <button @click="$store.commit('increate', 2)">Mutation</button> -->
<button @click="increate(2)">Mutation</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
export default {
computed: {
...mapState({
num: 'count',
message: 'msg'
}),
...mapGetters(['reverseMsg'])
},
methods: {
...mapMutations(['increate'])
}
}
</script>
Vue devtools 查看时光旅行和历史回滚
打开安装好的 chrome 浏览器插件: vue devtools,查看 Vuex:

- 左侧是 mutations 提交记录
- Base State 初始 state
- 右侧是 本次提交的 mutation 、state、getters
mutation 每次提交记录中提供3个操作,目的是为了方便调试,从右向左依次是:
- Time Travel to This State - 时光旅行
- 点击会跳转到本次 mutation 提交的时候
- Revert This Mutation - 历史回滚
- 回滚到本次提交之前,点击后,本次和之后的提交都被清空
- Commit This Mutation - 把本次提交,作为最后一次提交
- 点击后,会把本次记录重置为最后一次提交的记录,并向上重置,直到 Base State,清空没被重置的记录
Action
在Action中可以执行异步操作,在异步操作之后,如果需要修改状态,可以通过提交 Mutation 来修改 State(所有的状态更改都要通过 Mutation)。
Action 接收两个参数:
- context - 上下文,包含 state、getters、commit、dispatch 等属性
- payload - 额外的参数
调用 Store 中的 Action 和 通过 commit 调用 Mutation 一样,它需要通过 $store.dispatch 提交,它接收:
- Action 的名字作为第一个参数
- 第二个参数用于传递额外的参数,即 payload
可以通过 mapActions 把 Action 映射到组件的 methods 中,mapActions 返回的方法封装了 dispatch的调用。
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload)
}, 1000)
}
},
modules: {}
})
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
count:{{ num }}<br />
msg:{{ message }}
<h2>Getter</h2>
reverseMsg:{{reverseMsg}}
<h2>Mutation</h2>
<button @click="increate(2)">Mutation</button>
<h2>Action</h2>
<!-- <button @click="$store.dispatch('increateAsync', 3)">Action</button> -->
<button @click="increateAsync(3)">Action</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState({
num: 'count',
message: 'msg'
}),
...mapGetters(['reverseMsg'])
},
methods: {
...mapMutations(['increate']),
...mapActions(['increateAsync'])
}
}
</script>
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。
当应用变得非常复杂时,store 对象就有可能变得非常臃肿。
Vuex 可以把 单一状态树拆(store )分成多个模块(module)。
每个模块都可以拥有自己的 state、mutations、actions、getters,甚至嵌套子模块。
定义模块:
- 模块中只是定义并导出了相关的成员。
// src/store/products.js
const state = {
products: [
{ id: 1, title: 'iPhone 11', price: 8000 },
{ id: 1, title: 'iPhone 12', price: 10000 }
]
}
const getters = {
productCount (state) {
return state.products.length
}
}
const mutations = {
setProducts (state, payload) {
state.products = payload
}
}
const actions = {}
export default {
state,
getters,
mutations,
actions
}
// src/store/cart.js
const state = {}
const getters = {}
const mutations = {}
const actions = {}
export default {
state,
getters,
mutations,
actions
}
注册模块:
- 导入模块
- 在 modules 中注册模块
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 导入模块
import products from './products'
import cart from './cart'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
mutations: {
increate (state, payload) {
state.count += payload
}
},
actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload)
}, 1000)
}
},
// 注册模块
modules: {
products,
cart
}
})
注册完(可以打印 store 看看):
- 会把模块中的状态挂载到 $store.state 中
- 可以通过
$store.state.[模块名].[模块中state的属性名]方法
- 可以通过
- 把模块的 mutations、actions 记录到 $store 的内部属性
_mutations和_actions中_mutations和_actions以对象形式存放 Store 中所有的 mutations 和 actions- key 是 mutations / actions 的名称
- value 是所有 mutations / actions 同名的方法组成的数组
- 可以通过 $store.commit 直接提交模块中的 mutation
- 可以通过 $store.dispatch直接提交模块中的 action
- 把模块的 getters 记录到 $store 的 getters 中
- 它没有根据模块分类(类似state),也没有收集所有方法(类似 mutations / actions),它是平铺存储,注意避免命名冲突
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
<h2>Module</h2>
products:{{$store.state.products.products}}<br/>
products count:{{$store.getters.productCount}}<br/>
<button @click="$store.commit('setProducts', [])">Mutation</button>
</div>
</template>
当前所演示的模块中的 mutations、actions、getters 都直接存储在 Store 中。
如果想要模块有更好的封装度和复用性,可以给模块开启命名空间。
将来在视图中使用模块中的成员的时候,看起来更清晰一些。
模块命名空间
推荐使用命名空间的用法。
在模块导出的对象中,通过namespacing 开启命名空间。
// src/store/cart.js
const state = {}
const getters = {}
const mutations = {}
const actions = {}
export default {
// 开启命名空间
namespaced: true,
state,
getters,
mutations,
actions
}
// src/store/products.js 一样
打印 Store 对比,发现 mutations、actions、getters 中模块的成员名都添加了模块名/作为前缀:

在视图中通过 mapXxxx 方法映射模块的成员:
- 第一个参数是模块命名空间的名字,也就是在 Store 的 modules 中定义的模块的名字。
- 第二个参数依然是数组或对象
// src/app.vue
<template>
<div id="app">
<h1>Vuex - demo</h1>
<h2>Module</h2>
<!-- products:{{$store.state.products.products}}<br/>
products count:{{$store.getters.productCount}}<br/>
<button @click="$store.commit('setProducts', [])">Mutation</button> -->
products:{{products}}<br/>
products count:{{productCount}}<br/>
<button @click="setProducts([])">Mutation</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 模块的成员
...mapState('products', ['products']),
...mapGetters('products', ['productCount'])
},
methods: {
...mapMutations('products', ['setProducts'])
}
}
</script>
Vuex 严格模式
Vuex 约定所有状态的变更都应该通过提交 Mutation。
但是语法上,可以直接访问和修改 $store.state 中的属性。
这破坏了Vuex的约定。
如果在组件中直接修改 state,devtools 无法跟踪到状态的修改。
开启严格模式之后,在组件中直接修改 state ,虽然还是会生效,但是会抛出错误:
[vuex] do not mutate vuex store state outside mutation handlers.
# 不要在 mutation 之外修改 vuex 中的状态
在创建 Store 的时候通过 strict 选项开启:
// 创建并导出 Vuex中的 Store 对象并导出
export default new Vuex.Store({
strict: true,
// ...
})
<button @click="$store.state.msg = 'Yes'">strict</button>
{{msg}}
注意:不要在生产环境开启严格模式。
严格模式会深度检查状态树,检查不合规的状态改变,会影响性能
可以在开发环境启用严格模式,在生产环境关闭严格模式。
export default new Vuex.Store({
// 打包时根据环境变量进行处理
// npm run serve 时 NODE_ENV = development
// npm run build 时 NODE_ENV = production
strict: process.env.NODE_ENV !== 'production'
)}
Vuex 的插件
以购物车为例,用户的购物车商品信息是记录在本地 localStorage 中,以保证刷新页面,还能保留记录。
这样就需要在每个修改(增、删等)购物车商品信息的 mutation 中更新 localStorage 中的数据。
可以使用 Vuex 的插件简化这个操作。
- Vuex 的插件就是要给函数
- 这个函数接收一个 store 的参数
- 这个函数中可以注册(store.subscribe)一个函数,可以在每个 mutation 结束后执行
- subscribe 用于订阅 mutation
- 它注册的回调函数,会在每个 mutation 完成之后调用
- 通过 store 的 plugins 选项注册
- 插件要在创建 store 之前创建
例如:
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// subscribe 用于订阅 mutation
// 注册的回调函数,会在每个 mutation 完成之后调用
// mutaion -> {type, payload}
// type:'命名空间/mutaion的名字'
// payload:mutation接收的参数
// state:store的state,不是模块的
})
}
模拟简单的 Vuex
Vuex 基本结构
Vuex 模块包含:
- Store 类 - 用于实例化 Store
- install 函数 - 用于 Vue.use 注册 Vuex 插件
// src/myvuex/index.js
let _Vue = null
class Store {}
function install (Vue) {
_Vue = Vue
}
export default {
Store,
install
}
install
install 的工作是把创建Vue实例时传入的 store 对象,注入到Vue原型上的$store。
在所有组件中都可以通过 this.$store 获取到Vuex中的仓库,从而共享状态。
let _Vue = null
function install (Vue) {
_Vue = Vue
// 在install中获取不到Vue的实例
// 所以需要混入 beforeCreate 来获取Vue实例
// 从而拿到选项中的 store 对象
_Vue.mixin({
beforeCreate () {
// 判断$options中是否有store选项
// 组件实例没有store,不需要处理
if (this.$options.store) {
_Vue.prototype.$store = this.$options.store
}
}
})
}
Store 类
- 构造函数 - 接收一个选项对象
- 属性:
- state - 响应式的
- getters
- mutations
- actions
- 方法:
- commit - 提交 mutation
- dispatch - 分发 action
初始化:
- this.state - 初始化为选项中传入的 state 进行响应式处理后的对象
- this.getters
- 选项传入的 getters 是一个对象,对象属性的值都是一个方法
- 这些方法都接收一个 state 参数,并最终都有返回值
- 一般情况下就是对状态作简单的处理,把结果返回
- 这些方法的作用都是当访问getters中的成员的时候,去获取值
- 可以把这些方法,通过 Object.defineProperty 转换成 this.getters 对象中的 get 访问器
- 并将 state 传入
- this._mutations && this._actions - 直接存储 选项传入的对象
- 它们是内部属性,在commit 或 dispatch 方法中获取
- 在它们的名称前加上下划线前缀表示私有,不希望外部访问
- commit
- 接收两个参数
- type - this._mutations中方法的名字
- payload - 调用方法时传入的参数
- 内部调用 mutation 的方法,并传入:
- state - 当前实例的state
- payload
- 接收两个参数
- dispatch
- 参数与 commit 类似
- 内部调用 action 的方法,并传入:
- context - 上下文,也就是当前实例
- payload - 调用方法时传入的参数
class Store {
constructor(options) {
const { state = {}, getters = {}, mutations = {}, actions = {} } = options
// 对 state 进行响应式处理
this.state = _Vue.observable(state)
// this.getters 不需要原型
this.getters = Object.create(null)
// 将 getters 中的方法转换成 this.getters 对象的get 范文其
Object.keys(getters).forEach(key => {
Object.defineProperty(this.getters, key, {
// 获取 getters 中方法的执行结果
get: () => getters[key](state)
})
})
// mutations和actions 在commit 或 dispatch 方法中获取
// 所以它们应该是内部私有成员,下划线前缀标识私有,不希望外部访问
this._mutations = mutations
this._actions = actions
}
commit (type, payload) {
this._mutations[type](this.state, payload)
}
dispatch (type, payload) {
this._actions[type](this, payload)
}
}
到此Vuex模拟完成,替换vuex的模块地址为 myvuex的地址,可以测试效果。
Vuex状态管理精讲
本文深入解析Vuex,Vue.js的状态管理库,探讨其核心概念如Store、State、Getter、Mutation、Action和Module,以及如何在大型单页应用中有效管理和共享组件状态。
4075

被折叠的 条评论
为什么被折叠?



