Vuex、Pinia、Context、Redux
最近自学了React框架的基本知识,学完Redux后对这四种状态管理机制都有了基本了解,为了更好地理解这四种机制的差别,对笔记进行了整理和归纳,方便纵向和横向对比。
VUEX
Vuex 是 Vue.js 的官方状态管理库,专为复杂的中大型单页应用(SPA)设计。实际开发中是多个组件共享一个状态,可以使用vuex进行状态管理,负责组件中的通信,方便维护代码。
需要vuex的场景:
- 多个组件依赖同一状态(如用户登录信息)
- 不同组件需要修改同一份数据(如购物车商品)
- 需要记录状态变化历史(如时间旅行调试)
- 跨层级组件通信困难(避免深层次的
props/$emit
)
1.基础使用
- 在vue中添加vuex插件。可以通过vue-cil添加vuex,也可以通过npm安装vuex。
- 在开发环境开启严格模式,修改数据时就必须通过mutation来处理。
scss
strict:products.env.NODE_ENV !== 'production'
在项目的 src
目录下会多出一个 store
目录,目录下会有个 index.js
javascript
/* src/store/index.js */
// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'
// 把 Vuex 注册到Vue 上
Vue.use(Vuex)
export default new Vuex.Store({
// 在开发环境开启严格模式 这样修改数据 就必须通过 mutation 来处理
strict:products.env.NODE_ENV !== 'production',
// 状态
state: {
},
// 用来处理状态
mutations: {
},
// 用于异步处理
actions: {
},
// 用来挂载模块
modules: {
}
})
使用的时候就把store挂载到vue中,所有组件就可以从store中获取数据。
2.核心概念
state
- vuex中的公共状态,所有共享数据存储于此。
- 特点:响应式,类似组件的data
php
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
Vue.use( Vuex )
const store = new Vuex.Store({
state:{
products: [
{name: '鼠标', price: 20},
{name: '键盘', price: 40},
{name: '耳机', price: 60},
{name: '显示屏', price: 80}
]
}
})
new Vue({
el: '#app',
store,
render: h => h(App)
})
Getters
是vuex的计算属性,类似于computed。用于封装复杂逻辑或过滤数据(派生状态)。
getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
使用方法:
javascript
// 先写入getters
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: '张三',
age: 21,
},
getters: {
// 在这里对state进行包装
//state 状态 如果要使用 state 里面的数据,第一个参数默认就是 state ,名字随便取
decorationName(state) {
return `大家好我的名字叫${state.name}今年${state.age}岁`
},
},
})
//使用:
//1.导入:this.$store.getters[名称]
<template>
<div id="app">
{{ this.$store.getters.decorationName }}
</div>
</template>
//2.使用 mapGetters
<template>
<div id="app">
{{ decorationName }}
</div>
</template>
<script>
// 从 Vuex 中导入 mapGetters
import { mapGetters } from 'vuex'
export default {
name: 'App',
computed: {
// 将 getter 映射到当前组件的计算属性
...mapGetters(['decorationName'])
},
}
</script>
mutation
store中的状态不能直接对其进行操作,需要使用mutation来对store中的状态进行修改,方便集中监控数据的变化。
state的更新必须是 mutation来处理。mutations
对象中保存着更改数据的回调函数,该函数名官方规定叫type
, 第一个参数是state
, 第二参数是payload
, 也就是自定义的参数.
使用方法:
javascript
export default new Vuex.Store({
state: {
name: '张三',
age: 21,
},
mutations: {
// 在这里定义 方法
/**
* @param {*} state 第一个参数是 Store 中的状态(必须传递)
* @param {*} newName 传入的参数 后面是多个
*/
changeName(state, newName) {
// 这里简单举个例子 修改个名字
state.name = newName
},
},
actions: {},
modules: {},
})
//在组件中使用
<template>
<div id="app">
<button @click="handleClick">方式1 按钮使用 mutation 中方法</button>
{{ name }}
</div>
</template>
<script>
//1.this.$store.commit() 触发
// 从 Vuex 中导入 mapState
import { mapState } from 'vuex'
export default {
name: 'App',
computed: {
// 将 store 映射到当前组件的计算属性
...mapState(['name', 'age'])
},
methods: {
//用额外的方法使用vuex中的方法
handleClick() {
// 触发 mutations 中的 changeName
this.$store.commit('changeName', '小浪')
}
},
}
</script>
//2.使用 mapMutations将mutation的方法直接映射到methods中
<script>
// 从 Vuex 中导入 mapState
import { mapState, mapMutations } from 'vuex'
export default {
name: 'App',
computed: {
// 将 store 映射到当前组件的计算属性
...mapState(['name', 'age'])
},
methods: {
// 将 mutations 中的 changeName 方法映射到 methods 中,就能直接使用了 changeName 了
...mapMutations(['changeName'])
},
}
</script>
action
action用于处理异步任务,通过action触发mutation间接改变状态,不能直接使用mutation直接对异步任务进行修改
actions
中的回调函数的第一个参数是context
,是一个与store
实例具有相同属性和方法的对象
定义actions方法:
javascript
export default new Vuex.Store({
state: {
name: '张三',
age: 21,
},
mutations: {
// 在这里定义方法
// state 第一个参数是 Store 中的状态(必须传递)
// newName 传入的参数
changeName(state, newName) {
state.name = newName
},
},
actions: {
//context 上下文默认传递的参数
//newName 自己传递的参数
// 定义一个异步的方法 context是 store
changeNameAsync(context, newName) {
// 用 setTimeout 模拟异步
setTimeout(() => {
// 在这里调用 mutations 中的处理方法
context.commit('changeName', newName)
}, 2000)
},
},
modules: {},
})
使用:
方法1:this.$store.dispatch()
xml
//1.this.$store.dispatch()
<script>
// 从 Vuex 中导入 mapState mapMutations
import { mapState, mapMutations } from 'vuex'
export default {
name: 'App',
computed: {
// 将 store 映射到当前组件的计算属性
...mapState(['name', 'age'])
},
methods: {
//用一个新的方法间接调用
changeName2(newName) {
// 使用 dispatch 来调用 actions 中的方法
this.$store.dispatch('changeNameAsync', newName)
}
},
}
</script>
方法2:mapAction
xml
<script>
// 从 Vuex 中导入 mapState mapMutations mapActions
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
name: 'App',
computed: {
// 将 store 映射到当前组件的计算属性
...mapState(['name', 'age'])
},
methods: {
// 直接映射 actions 中的指定方法 到 methods中,就可以在该组件直接使用
...mapActions(['changeNameAsync'])
},
}
</script>
module
为了避免在一个复杂的项目state中的数据变得臃肿,vuex允许将store分成不同的模块,每个模块都有属于自己的 state
,getter
,action
,mutation
javascript
//在index.js中挂载其他模块
//创建模块animal
/* animal.js */
const state = {
animalName: '狮子',
}
const mutations = {
setName(state, newName) {
state.animalName = newName
},
}
//导出
export default {
state,
mutations,
}
在index.js中引入:
javascript
/* src/store/index.js */
// 引入模块
import animal from './animal'
// 把 Vuex 注册到Vue 上
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
animal,
},
})
然后就可以在组件中使用
方法1 :$store.state在module中挂载的模块名
javascript
//1.$store.state[在module中挂载的模块名][挂载的模块里的属性]
<template>
<div id="app">
{{ this.$store.state.animal.animalName }}
<button @click="$store.commit('setName', '老虎')">改名</button>
</div>
</template>
//也可以使用mapXXX进行映射
//需要在导出时添加命名空间namespaced: true
/* animal.js */
const state = {
animalName: '狮子',
}
const mutations = {
setName(state, newName) {
state.animalName = newName
},
}
export default {
// 开启命名空间 方便之后使用 mapXXX
namespaced: true,
state,
mutations,
}
方法2 :使用mapState
把属性导入到computed中,使用mapMutations
把mutation中的方法导入到methods
xml
//2.
<template>
<div id="app">
{{ animalName }}
<button @click="setName('老鹰')">改名</button>
</div>
</template>
<script>
// 从 Vuex 中导入 mapState mapMutations
import { mapState, mapMutations } from 'vuex'
export default {
name: 'App',
computed: {
// mapState 使用方式和之前有些许不同,第一个是module挂载的模块名
// 第二个参数是 animal 模块中的 state 属性
...mapState('animal', ['animalName'])
},
methods: {
// mapMutations 使用方式也和之前有些许不同,第一个是module挂载的模块名
// 第二个参数是 animal 模块中的 mutation 方法
...mapMutations('animal', ['setName'])
},
}
</script>
3.工作流程
1.组件通过dispatch触发action
2.action执行异步操作
3.action通过commit提交mutation
4.mutation同步修改state
5.state变化触发所有依赖它的组件更新

Pinia
概念:pinia是vue的存储库,允许跨组件/页面共享,保留了 Vuex 的核心功能,将mutations与actions结合,提供了更简洁的 API 和更好的TypeScript 支持。
核心优势:
- API简洁性,去掉了mutations
- typescript支持
- 模块化,自动按照文件分割模块
- composition api:原生支持,与setup()完美结合
- 体积更轻量
- devtools支持
1.核心概念
store(存储)
核心单元,每个store是一个独立的状态容器
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++ // 直接修改状态,无需 mutations
}
}
})
使用store
xml
<script setup>
// 引入定义
import useFormInfoStore from '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
</script>
解构store
xml
<script setup>
import { storeToRefs } from 'pinia';
// 引入定义
import useFormInfoStore from '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
const { name, age } = formInfoStore; // 此时解构出来的name和age不具有响应式
const { name, age } = storeToRefs(formInfoStore); //此时解构出来的name和age是响应式引用
</script>
state(状态)
使用函数返回初始状态(类似 Vue 的 data()
),直接通过 store
实例访问和修改。
scss
const store = useCounterStore()
console.log(store.count) // 访问
store.count++ // 直接修改(仍可追踪)
使用:
一般可以通过store实例来直接读取和写入状态:
xml
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
console.log(formInfoStore.name); // 'Hello World'
formInfoStore.age++; // 19
</script>
还提供了一些常见场景的方法来操作:
$reset()
重置状态变为初始值$patch({param1,param2})
对状态批量修改$state(newState)
通过将$state 属性设置为新对象来替换 Store 的整个状态$subscribe()
可以订阅store中的状态变化
xml
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
console.log(formInfoStore.name); // 'Hello World'
// 直接修改state中的属性
formInfoStore.age++; // 19
// 1.$reset 重置状态,将状态重置成为初始值
formInfoStore.$reset();
console.log(formInfoStore.age); // 18
// 2.$patch 支持对state对象的部分批量修改
formInfoStore.$patch({
name: 'hello Vue',
age: 198
});
// 3.$state 通过将其 $state 属性设置为新对象来替换 Store 的整个状态
formInfoStore.$state = {
name: 'hello Vue3',
age: 100,
gender: '男'
}
// 4.$subscribe 订阅store中的状态变化
formInfoStore.$subscribe((mutation, state) => {
// 监听回调处理
}, {
detached: true // 💡如果在组件的setup中进行订阅,当组件被卸载时,订阅会被删除,通过detached:true可以让订阅保留
})
</script>
Getters(计算属性)
类似 Vue 的 computed
,自动缓存结果
javascript
getters: {
doublePlusOne() {
return this.double + 1 // 可调用其他 getter
}
}
通常getter是不支持额外传参的,但是我们可以从getter返回一个函数的方式来接收参数:
javascript
import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {
state: () => {
return {
name: 'Hello World',
age: 18,
isStudent: false,
gender: '男'
};
},
getters: {
isLargeBySpecialAge: (state) => {
return (age) => {
return state.age > age
}
}
}
});
export default useFormInfoStore;
actions(操作)
包含同步异步操作,可以直接修改state
csharp
actions: {
async fetchData() {
const res = await api.getData()
this.data = res // 直接赋值
}
}
$onAction()
可以使用 store.$onAction()
订阅 action 及其结果。传递给它的回调在 action 之前执行。 after处理 Promise 并允许您在 action 完成后执行函数,onError允许您在处理中抛出错误。
javascript
const unsubscribe = formInfoStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()
2.对比
- Vue2和Vue3都支持,这让我们同时使用Vue2和Vue3的小伙伴都能很快上手。
- Vuex中需要在actions中调用mutation修改state,但Pinia中可以直接修改state,实现同步异步操作,省略了mutation
- Pinia中只有state、getter、action ,抛弃了Vuex中的Mutation,减少了工作量,更轻量
- Pinia中action支持同步和异步,Vuex不支持
- 良好的Typescript支持,Vue3都推荐使用TS来编写,这个时候使用Pinia就非常合适了
- 无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。
- 体积非常小,只有1KB左右。
- pinia支持插件来扩展自身功能。
- 支持服务端渲染。
Context
Context是React提供的全局数据共享机制,用于在组件树中跨层级传递数据,不必通过 props一层层传递。
适用于存储:
- 全局配置:主题、语言、用户认证状态
- 共享服务:API客户端、日志服务
- 复杂状态:购物车、用户偏好设置
创建Context
javascript
import React from 'react'
// 创建一个context对象,参数是默认值,没有匹配到Provider的时候会提供这个值
const ThemeContext = React.createContext('light')
- 创建后得到
{ Provider, Consumer }
对象
Provider
在顶层提供数据
xml
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
Consumer
可以在任意组件中使用,有两种使用方式
方式1 :useContext
(推荐)
javascript
import { useContext } from 'react'
function ChildComponent() {
const theme = useContext(ThemeContext)
return <div>当前主题是 {theme}</div>
}
方式2 :Context.consumer
xml
<ThemeContext.Consumer>
{theme => <div>当前主题是 {theme}</div>}
</ThemeContext.Consumer>
缺点
Provider
的value发生变化时,所有使用了这个context的组件都会重新渲染,尽管有的组件没有使用到变化的属性。
javascript
<ThemeContext.Provider value={{ theme: 'dark', toggle: fn }}>
<CompA /> // 只使用了theme
<CompB /> // 只使用了toggle
</ThemeContext.Provider>
解决方案:使用useMemo
包裹value,减少不必要的更新
scss
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme])
-
难以做到模块化、精细化更新:
context是一个全局单元,当管理的数据过多时,很难拆分、复用其中的数据,而Redux和Pinia可以做到模块化管理。
Redux
概念
redux是JavaScript应用的可预测状态容器,专为管理复杂应用状态而设计。
- 单一数据源:它会将整个应用状态(数据)存储到一个地方,称为store。store中保存了一颗状态树(state tree)。
- 状态只读 :组件改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,state才完成更新
- 使用纯函数执行修改 :
reducer
是纯函数,接收旧的state和action,返回新的state - 组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件
- 其他组件则通过订阅store中的状态来刷新自己的视图
工作流程:
-
view
层触发dispatch(action)- action包含type和data
-
store
调用reducer函数,传入当前的state和action- reducer用于出书化妆台、加工状态
-
reducer
返回新的state -
store
保存新的state并通知所有订阅的组件 -
更新视图
:组件根据新state重新渲染

基础实现
-
创建store:使用createStore函数创建一个store,传入一个reducer函数
iniconst store = createStore(todosReducer);
-
定义reducer:reducer是一个纯函数,根据action的类型更新状态。
goconst reducer = (state=10,action) => { switch(action.type) { case 'INCREMENT': return state+1; case 'DECREMENT': return state-1; default:return state; } }; <button onClick={()=>{ store.dispatch({type:"INCREMENT",val:10}); }}> 点击自增 </button>
-
派发action:使用 store.dispatch 方法派发 action,触发状态更新。
less// 分发action store.dispatch(addTodo('Learn Redux')); store.dispatch(toggleTodo(1));
-
订阅store:使用 store.subscribe 方法订阅 store 的变化,当状态更新时执行回调函数。
javascript// 订阅状态变化 let unSubscribe = store.subscribe(() => { console.log('Current state:', store.getState()); }); //函数返回一个方法,调用可以直接解除监听 unSubscribe();
比较
特性 | Vuex | Pinia | React Context | Redux |
---|---|---|---|---|
设计理念 | 集中式状态管理 | 轻量级状态管理 | 组件树值传递机制 | 可预测状态容器 |
状态存储 | 单一 store | 多个 store | 多个 Context | 单一 store |
API 复杂度 | 较高(4个概念) | 极低(3个概念) | 低(Provider/Consumer) | 高(action/reducer/store) |
异步处理 | Actions 提交 mutations | Actions 直接修改状态 | 需自行实现 | 需中间件(redux-thunk等) |
TypeScript | 支持但需额外配置 | 一流支持 | 内置支持 | 需额外类型定义 |
响应式原理 | Vue 响应式系统 | Vue 响应式系统 | 无自动响应,需要手动触发更新 | 无自动,基于reducer响应 |
修改数据方式 | mutation/action | action(可直接修改) | setState或回调 | dispatch + action |
模块化 | 命名空间 modules | 天然多 store 设计 | 多 Context 隔离 | combineReducers |
开发体验 | 模板代码多 | 简洁直观 | 简单但功能有限 | 模板代码多(Redux Toolkit改善) |