Vuex简介
介绍
Vuex 是一个专为 Vue.js 应用程序开发的状态(数据)管理模式。在Vue中实现集中式状态管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),并以相应的规则保证状态以一种可预测的方式发生变化。也是一种组件间通信的方式,且适用于任意组件间通信。
在vue开发中,每个组件都有自己的独立的数据,整个项目中的所有组件可以通过bus总线进行传值,但是如果出现组件之间需要共用同一组数据时,数据管理就会非常麻烦。vuex是vue的状态(数据)管理工具,它采取了一种集中管理数据的思想,将整个项目中所有的公共数据放在一个统一的仓库中,然后任何组件都可以从这个仓库中读取数据,也可以通过仓库提供的方法修改数据。
什么是“状态管理模式”?
让我们从一个简单的 Vue 计数应用开始:
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{
{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
以下是一个表示“单向数据流”理念的简单示意:

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。
以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
这就是 Vuex 背后的基本思想,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。
什么情况下我应该使用 Vuex?
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
使用场景总结为:
- 多个组件依赖于同—状态
- 来自不同组件的行为需要变更同—状态
官网
Vuex目前主要有两个版本v3.x和v.4x。
-
Vue2.x对应Vuex的v3.x版本
-
Vue3.x对应Vuex的v4.x版本
v3版本的官网:https://v3.vuex.vuejs.org/zh/
v4版本的官网:https://vuex.vuejs.org/zh/
安装
以Vue2中的方式使用为例:
方式一:在浏览器中直接使用,需要使用script标签导入vue-router.js
<script src="https://unpkg.com/vuex@3"></script>
方式二:在脚手架中使用
安装vue-router的时候需要指定版本
- vue2.x对应vuex3.x:
npm install vuex@3
- vue3.x对应vuex4.x:
npm install vuex@4
Vuex的使用
安装vuex
npm install vuex@3
导入vuex并使用
在src目录中添加store文件夹,在store文件夹中创建index.js
,在index.js
中安装Vuex,创建store实例
- 在一个模块化的打包系统中,您必须显式地通过
Vue.use()
来安装 Vuex:
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
新建Store仓库对象
在store文件夹中的index.js
中创建仓库实例store并导出:
// store/index.js
const store = new Vuex.Store({
// 在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到
strict: process.env.NODE_ENV !== 'production',
// 设置仓库的初始状态:初始数据
state:{
count: 1,
base: 10,
message: 'Hello Vuex!',
},
// getter 就像计算属性一样,只有当它依赖的值发生了改变才会被重新计算。
// 1、一般是做数据处理:排序、保留小数点等
// 2、不需要直接改变getters中的属性,getters中所依赖的数据发生变化时,getters中的属性会自动计算
getters:{
bigCount: state => state.count * 2,
baseCount: state => state.count * state.base,
}
// mutation: 定义一些函数,作用:改变state中数据
// 1、改变state中数据唯一的方法就是提交mutation
// 2、在mutation中的所有操作都必须是同步的
mutations:{
INCREASE_COUNT(state, value) {
state.count += value;
},
DECREASE_COUNT(state, value) {
state.count -= value;
},
SET_BASE(state, value) {
state.base = value;
},
}
// action: 类似mutation,也是定义一些函数,和mutation有些不一样
// 1、action中不能直接改变state,需要在action中提交mutation
// 2、action中可以包含异步操作:ajax请求等
actions:{
setCount(context, data) {
if (data.type == 0) {
context.commit('INCREASE_COUNT', data.value);
} else {
context.commit('DECREASE_COUNT', data.value);
}
},
// 不停的点击按钮时候,让count的值每隔一秒种增加或者减少1
setCountWaitOneSecond({
dispatch }, data) {
setTimeout(() => {
dispatch('setCount', data); //在一个action中分发另外一个action
}, 1000);
},
setCountWidthBase({
dispatch, state }, data) {
dispatch('setCount', {
type: data.type, value: state.base });
},
}
});
export default sotre;
在仓库store实例中添加数据,vuex的状态,就是数据
- state 仓库中数据存储位置,
- getters 仓库数据的处理,类似于组件的computed
- mutations 同步方式改变仓库的数据,通过commit提交mutation来改变state中的数据
- actions 异步方式修改仓库的数据,通过dispatch触发action的异步操作来修改state,在action中依旧需要提交mutation才能修改state
状态的访问与修改
store状态的访问与修改有两种方式:
- 在JS模块中
- 在vue组件中
在JS模块中使用store
在JS模块中访问状态比较简单,只需要导入store实例,就可以直接访问和修改状态:
// 在任意js文件中导入store实例
import store from './store'
// 访问仓库的状态state
store.state.count
// 访问仓库的getters
store.getters.bigCount
// 提交mutation改变状态
store.commit('INCREASE_COUNT', 1);
// 分发action
store.dispatch('setCount', {
type:0, value: 1});
在vue组件中使用store
Vue的组件有很多,并且所有的组件都是根组件 new Vue
的后代组件,如果按照在JS模块的方式导入store实例,显然很麻烦,不过vue提供了一种很方便的方式在每一个组件中访问store实例。
为了在 Vue 组件中访问 this.$store
,你需要为 Vue 实例提供创建好的 store。Vuex 提供了一个从根组件向所有子组件,以 store
选项的方式“注入”该 store 的机制:
new Vue({
// 在根组件挂载store实例之后,在任何一个子组件中都能通过 this.$store 访问 store 实例
store,//挂载store
render: h => h(App),
}).$mount('#app')
这样,就可以开心的在任意一个组件中通过 this.$store
来访问和修改状态
- 在组件内部
this.$store //获取store实例
this.$store.state.count //访问state的数据
this.$store.getters.bigCount // 访问getters的数据
this.$store.commit('INCREASE_COUNT', 1); //提交mutation
this.$store.dispatch('setCount', {
type:0, value: 1}); // 分发action
- 在组件的模板中
<!-- 访问state的数据 -->
<p>count = {
{$store.state.count}}</p>
<!-- 访问getters的数据 -->
<p>bigCount = {
{$store.getters.bigCount}}</p>
<!-- 提交mutation -->
<button @click="$store.commit('INCREASE_COUNT', 1);">提交mutation</button>
<!-- 分发action -->
<button @click="$store.dispatch('setCount', {type:0, value: 1});">提交mutation</button>
核心概念
store
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
Vuex 和单纯的全局对象有以下两点不同:
1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
2、你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
new Vuex.Store({
});
State
单一状态树
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
存储在 Vuex 中的数据和 Vue 实例中的 data
遵循相同的规则。
new Vuex.Store({
state: {
count: 1,
base: 10,
message: 'Hello Vuex!',
},
});
访问State
State 会暴露为 store.state
对象,你可以以属性的形式访问这些值:
store.state.count
函数形式的State
在模块化的vuex中,将 store 分割成模块(module)。每个模块拥有自己的 state,此时state应该是一个函数,与组件实例的data是一个函数保持一致:
new Vuex.Store({
state:() => ({
count: 1,
base: 10,
message: 'Hello Vuex!',
}),
});
组件仍然保有局部状态
使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
Getters
vue的计算属性
有时候我们需要从 store 中的 state 中派生出一些状态,例如对count进行翻倍处理:
computed: {
bigCount () {
return this.$store.state.count * 2;
}
}
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
store的计算属性
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。
就像组件的计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数:
new Vuex.Store({
getters: {
bigCount: state => state.count * 2,
baseCount: state => state.count * state.base,
},
});
访问Getter
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值:
store.getters.bigCount
Getter的其他参数
Getter 也可以接受其他 getter 作为第二个参数:
getters: {
// ...
doubleBaseCount: (state, getters) => {
return getters.baseCount * 2
}
}
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
Mutations
要记住:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
注册mutation
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
INCREASE_COUNT (state) {
// 变更状态
state.count++
}
}
})
提交mutation
你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 INCREASE_COUNT
的 mutation 时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:
store.commit('INCREASE_COUNT')
提交载荷(Payload)
你可以向 store.commit
传入额外的参数,即 mutation 的 载荷(payload):
// ...
mutations: {
INCREASE_COUNT (state, n) {
state.count += n
}
}
store.commit('INCREASE_COUNT', 10)
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('INCREASE_COUNT', {
amount: 10
})
对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type
属性的对象:
store.commit({
type: 'INCREASE_COUNT',
amount: 10
})
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
mutations: {
increment (state, payload)