文章目录
一、简介
我们来看看对 Vuex 比较专业的介绍:
Vuex 是一个专为 Vue 开发的应用程序的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简而言之,Vuex 采用类似全局对象的形式来管理所有组件的公用数据,如果想修改这个全局对象的数据,得按照Vuex提供的方式来修改(不能自己随意用自己的方式来修改)。
二、优点
Vuex状态管理跟使用传统全局变量的不同之处:
-
Vuex的状态存储是响应式的: 就是当你的组件使用到了这个 Vuex 的状态,一旦它改变了,所有关联的组件都会自动更新相对应的数据,这样开发者省事很多。
-
不能直接修改Vuex的状态: 如果是个全局对象变量,要修改很容易,但是在 Vuex 中不能这样做,想修改就得使用 Vuex 提供的唯一途径:显示地提交(
commint
)mutations
来实现修改。这样做的好处就是方便我们跟踪每一个状态的变化,在开发过程中调试的时候,非常实用。
三、使用步骤
1. 安装Vuex
npm install vuex --save
2. 引用Vuex
在App.vue文件中:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
3. 创建仓库Store
要使用 Vuex,我们要创建一个实例 store
,我们称之为仓库,利用这个仓库 store
来对我们的状态进行管理。
//创建一个 store
const store = new Vuex.Store({});
四、包含模块
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从 store 中获取数据,
mapGetters
辅助函数仅仅是将 store 中的getter
映射到局部计算属性。 - Mutation:是唯一更改 store 中状态的方法,而且要是
同步函数
。 - Action:用于提交
mutation
,而不是直接变更状态,可以包含任意异步操作。 - Module:可以将 store 分割成模块(module)。每个模块拥有自己的
state
、mutation
、action
、getter
、甚至是嵌套子模块
Vuex的作用类似全局对象,Vuex 使用单一状态树,用一个对象State包含了整个应用层级的所有状态,你可以理解为这些状态就是一堆全局变量和数据。
1. State
假设我们有一个全局状态 count
的值为 5。那么,我们就可以将其定义为 state
对象中的 key
和 value
,作为全局状态供我们使用。如下:
-
Vue2.x中:
//创建一个 store const store = new Vuex.Store({ //state存储应用层的状态 state:{ count:5 //总数:5 } });
-
Vue3.x中:
import { createStore } from 'vuex' export default createStore({ state: { count:5 //总数:5 } }
2. Getters
可以认为,getters
是store的计算属性,类似于computed
,对state里的数据进行一些过滤,改造等等
假设我们要在state.count
的基础上派生出一个新的状态newCount
出来,就适合使用我们的 getters
getters
接受 state
作为其第一个参数
-
Vue2.x中:
const store = new Vuex.Store({ //state存储应用层的状态 state:{ count:5 //总数:5 }, getters:{ newCount:state => state.count * 3 } });
在组件中获取
{{newCount}}
方式:export default { computed: { newCount(){ return this.$store.getters.newCount; } } };
-
Vue3.x中:
import { createStore } from 'vuex' export default createStore({ state: { count:5 //总数:5 }, getters: { newCount:state => state.count * 3 }, }
在组件中获取
{{newCount}}
方式:<template> <span>{{ $store.getters.newCount }}</span> </<template> <script setup> import { useStore } from 'vuex'; const store = useStore(); const getNewCount= () => { console.log(store.getters.newCount); }; </script>
3. Mutations
Vuex
给我们提供修改仓库 store
中的状态的唯一办法就是通过提交mutation
mutation必须是同步函数
,这是 Vuex 设计时的一个核心原则:
- Vuex 的状态跟踪机制依赖于同步的 mutations
- 当 mutation 异步执行时,Vuex 无法准确地知道何时状态已经更新,这可能导致无法正确地显示状态变化
在 mutations 中,可以直接修改state状态
-
Vue2.x中:
const store = new Vuex.Store({ //state存储应用层的状态 state:{ count:5 //总数:5 }, // mutations是修改state中数据的唯一途径 mutations:{ increment(state,value){ state.count += value; } } });
-
Vue3.x中:
import { createStore } from 'vuex' export default createStore({ state: { count:5 //总数:5 }, mutations:{ increment(state,value){ state.count += value; } } }
通过 commit 方法被调用,通常是在组件中通过 this.$store.commit("increment", 666);
的方式触发
我们在提交commit
时候,第一个参数"increment"
,就是对应在 mutations
中的increment
方法,第二个参数是自定义值。例如:
-
Vue2.x中:
methods: { getVal(event) { //通过commit提交一个名为increment的mutation this.$store.commit("increment", 666); } }
-
Vue3.x中:
import { useStore } from 'vuex'; const store = useStore(); const getVal = event => { //通过commit提交一个名为increment的mutation store.commit("increment", 666); }
4. Action
- Actions 是可以包含任意异步操作的函数
- 在 actions 中,不能直接修改状态(state),而需要通过 dispatch 方法提交一个 mutation 来更改状态
- 只有通过
action => mutations => states
,这个流程进行操作,具体步骤如下:
export default new Vuex.Store({
//存放数据
state: {
obj: {},
},
//4. 通过commit mutations中的方法来处理
mutations: {
getParam(state, value) {
//5.修改state中的数据
state.obj = value
}
},
//2.接受dispatch传递过来的方法和参数
actions: {
getParamSync(store, object) {
// 异步操作(例如网络请求)
axios.post('https://api.example.com/submit',{data: object}).then(function (response) {
const value = response.data; // 从服务器获取的数据
//3.通过commit提交一个名为getParam的mutation
//action 函数接收一个 store 的实例对象,因此你可以调用 store.commit 提交一个 mutation
store.commit('getParam', value);
})
}
}
})
通过 dispatch 方法被调用,通常是在组件中通过 this.$store.dispatch('getParamSync',{name,age,sex})
的方式触发
methods: {
getVal() {
let name= 'xia';
let age= '26';
let sex= 'man';
//1.通过dispatch将方法getParamSync和多个参数{name,age,sex}传递给actions
this.$store.dispatch('getParamSync',{name,age,sex})
}
}
5. Modules
随着项目的复杂度增大,为了方便管理 Vuex,一般会将其按功能分割成不同的模块(Module
),方便日后管理。每个模块拥有自己的 state
、mutation
、action
、getter
甚至是嵌套子模块
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import * as getters from './getters'
import moduleA from './module/moduleA' // 模块A
import moduleB from './module/moduleB' // 模块B
Vue.use(Vuex)
export default new Vuex.Store({
actions,
getters,
state,
mutations,
modules: {
moduleA,
moduleB
}
})
moduleA.js
/ moduleB.js
文件
// 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
export default {
state: {
text: 'moduleA'
},
getters: {},
mutations: {},
actions: {}
}
然后我们就在组件里这么调用就可以了
<template>
<div class="demo">
<h1>{{getText1}}</h1>
<h1>{{getText2}}</h1>
</div>
</template>
computed: {
getText1(){
return this.$store.state.moduleA.text;
},
//或
...mapState({
getText2: state => state.moduleB.text;
})
}
由此可知,模块内部的 state 是局部的,只属于模块本身所有,所以外部必须通过对应的模块名进行访问。
五、Vuex最最简单的项目实例
1. store文件目录结构
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import * as getters from './getters'
//每次修改state都会在控制台打印log
import createLogger from 'vuex/dist/logger'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
actions,
getters,
state,
mutations,
strict: debug, // 当debug=true时开启严格模式(性能有损耗)
plugins: debug ? [createLogger()] : []
})
state.js
const state = {
news: {}
}
export default state;
mutations.js
const mutations = {
SET_NEWS(state, val) {
state.news= val
}
}
export default mutations;
actions.js
//异步处理
const actions = {
M_NEWS({ commit }, object) {
// 异步操作(例如网络请求)
axios.post('https://api.example.com/submit',{data: object}).then(function (response) {
const value = response.data; // 从服务器获取的数据
commit('SET_NEWS', value); // commit mutations修改
})
}
}
export default actions;
getters.js
// 通常通过getters取数据 (this.$store.getters.news;)
export const news = state => state.news // 不做其他处理 直接映射出去
2. 使用store
在 main.js
中引用
import store from './store' //vuex存储文件
new Vue({
el: '#app',
router,
store,
components: {
App
},
template: '<App/>'
})
3. 操作数据
运用vuex语法糖 mapState
、mapGetters
和 mapMutations
-
mapState
:映射 state 到组件的计算属性:...mapState(["news"])
就相当于this.$store.state.news
-
mapGetters
:映射 getters 到组件的计算属性:...mapGetters(["news"])
就相当于this.$store.getters.news
-
mapMutations
:映射 mutations 到 组件的 methods:...mapMutations([{ changeNews: "SET_NEWS" }])
就相当于给 SET_NEWS 重命名为 changeNews
this.changeNews(val)
就相当于this.$store.commit("SET_NEWS", val)
<template>
<div>
<p>News: {{ news }}</p>
<button @click="submit">ChangeNews</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from "vuex";
export default {
computed: {
// 映射 state 到组件的计算属性
...mapState(["news"]) // 相当于 this.$store.state.news (vuex语法糖)
// 或者
// 映射 getters 到组件的计算属性
...mapGetters(["news"]) // 相当于 this.$store.getters.news (vuex语法糖)
},
methods: {
...mapMutations([{
// 将 changeNews 与 mutations中的 SET_NEWS 关联
changeNews: "SET_NEWS" // 相当于给 SET_NEWS 重命名为 changeNews
}]),
submit () {
let val = 'test news';
this.changeNews(val); // 相当于 this.$store.commit("SET_NEWS", val);
}
}
}
</script>
六、Vue3 setup 中使用
在 Vue2 项目中可以使用 this.$store
获取 vuex 里的数据和保存全局数据,但是在 Vue3 的 setup 函数里,并没有 this
这个概念,这里可以使用 useStore
代替,具体使用如下:
<template>
<div>
<p>News: {{ news }}</p>
<button @click="changeNews">Change News</button>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
// 使用 useStore 钩子获取对 Vuex store 的引用
const store = useStore();
// 创建一个响应式引用来存储 news
const news = ref(store.state.news);
// 定义一个方法来改变 news
const changeNews = () => {
// 提交 mutation 来改变 news
store.commit('SET_NEWS', 'test news');
};
// 返回需要在模板中使用的响应式引用和方法
return {
news,
changeNews,
};
},
};
</script>
store/index.js
:
// Vue2.x 中创建 store
// const store = new Vuex.Store({
// state:{
// count:5 //总数:5
// }
// });
// Vue3.x 中通过 createStore 创建 store
import { createStore } from 'vuex'
export default createStore({
state: {
news: null
},
mutations: {
SET_NEWS: (state, value) => {
state.news = value;
}
},
getters: {
news : state => { state.news || sessionStorage.news }
},
actions: {
getData(store) {
// 模拟异步
setTimeout(() => {
store.commit('SET_NEWS', '');
}, 2000);
}
},
modules: {}
})
七、为什么不能在 Mutation 中处理异步操作?
-
破坏状态追踪
Vuex 的 DevTools 依赖于同步的 Mutation来准确记录状态变化。如果 Mutation 是异步的,DevTools 无法确定状态何时更新,导致调试工具的时间旅行(Time Travel)功能失效。 -
不可预测的状态变更
Mutation 的设计初衷是同步修改状态。如果 Mutation 包含异步操作,组件在提交 Mutation 后无法立即知道状态何时被修改,可能导致视图渲染不一致或依赖状态的逻辑出错。 -
破坏设计原则
Vuex 强制通过 Mutation → State 的同步流程,确保状态变化的来源可追踪。Action 作为异步入口,负责协调外部操作(如API调用)和提交 Mutation。
反例:在 Mutation 中写异步(错误!)
mutations: {
// 错误示例!Mutation 必须是同步的
async SET_DATA(state) {
const data = await fetchData(); // 异步操作
state.data = data; // 状态变更时机不确定
}
}
正确流程:Action → Mutation → State
-
组件通过 dispatch 触发 Action。
-
Action 执行异步操作(如API请求)。
-
Action 在异步操作完成后,通过 commit 提交 Mutation。
-
Mutation 同步修改 State。
-
State 变化触发视图更新。
其他
关于 Pinia(Vuex 的进化版本)
-
Pinia的设计灵感来源于 Vuex,但相比Vuex,Pinia更加轻量级、简单易用和灵活。
-
Pinia 是 Vuex 的进化版本,由 Vue 核心团队维护,并推荐用于新项目。
-
Vuex 的未来:Vue 官方已明确表示,Pinia 的实现对 Vuex 5 的提案有直接影响(Vuex 5 的提案已冻结,Pinia 成为事实上的“Vuex 5”)。
-
Pinia是一个基于Vue 3的状态管理库,专为Vue 3设计。它提供了一种简单、直观且可扩展的方式来组织和访问应用程序的状态。Pinia的设计灵感来源于Vuex,但相比Vuex,Pinia更加轻量级、简单易用和灵活。Pinia利用Vue 3的新特性,如Composition API,使得开发者能够更容易地上手和使用。
-
Pinia 同时支持 Vue 2 和 Vue 3,而 Vuex 4 仅支持 Vue 3。
Pinia 与 Vuex 区别
特性 | Vuex | Pinia |
---|---|---|
设计原则 | 基于选项式 API 设计,使用 mutations、actions、getters 等概念管理状态,结构相对复杂。mutations 是唯一修改 state 的地方 | 采用组合式 API 风格,以函数定义 store,无需 mutations,直接用 actions 修改 state,代码简洁直观 |
性能表现 | 架构相对复杂,处理大量状态更新时可能存在一定性能开销。 | 性能较好,实现轻量级,无复杂架构,采用 Proxy 实现响应式,状态更新高效。 |
版本兼容性 | Vue 2, Vue 3 | 主要针对 Vue 3 |
状态管理 | 使用全局单一的 Store 来管理应用的状态 | 使用独立的 store(与组件类似)来管理状态 |
状态结构 | 模块化的,通过命名空间区分 | 每个 store 都是独立的,易于测试和复用 |
Mutations | 强制使用,mutations 是唯一修改 state 的地方 | 不需要 mutations,直接通过 actions 或 state 的直接赋值来更新状态 |
Actions | 用于异步操作或包含任意副作用的操作 | 类似于 Vuex 的 actions,但更简洁,可以直接在 actions 中修改状态 |
TypeScript 支持 | 支持 TypeScript,但因选项式 API 设计,类型定义复杂,需编写更多类型声明代码。 | 对 TypeScript 支持友好,基于组合式 API 设计,类型推导自然准确,定义 store 时易进行类型注解。 |
插件系统 | 支持插件扩展 | 支持通过 middleware(中间件)或 plugins(插件)来扩展功能 |
热模块替换 (HMR) | 支持,但需要额外配置 | 支持,集成在 Pinia 内部 |
开发体验 | 需要额外的学习成本,尤其是 mutations 的使用 | 接近 Vue 3 的 Composition API,学习成本低 |
体积 | 相对较大,包含额外的功能和抽象 | 较小,只包含核心功能 |
社区支持 | 成熟的社区和大量的教程/文档 | 相对较新的项目,但正在迅速获得支持 |
适用场景 | 大型、复杂的应用 | 中小型到大型应用,尤其是需要更灵活状态管理的场景 |
.