当前位置: 首页 > news >正文

【Vue】状态管理(Vuex、Pinia)

在这里插入图片描述

个人主页:Guiat
归属专栏:Vue

在这里插入图片描述

文章目录

  • 1. 状态管理概述
    • 1.1 什么是状态管理
    • 1.2 为什么需要状态管理
  • 2. Vuex基础
    • 2.1 Vuex核心概念
      • 2.1.1 State
      • 2.1.2 Getters
      • 2.1.3 Mutations
      • 2.1.4 Actions
      • 2.1.5 Modules
    • 2.2 Vuex辅助函数
      • 2.2.1 mapState
      • 2.2.2 mapGetters
      • 2.2.3 mapMutations
      • 2.2.4 mapActions
      • 2.2.5 createNamespacedHelpers
    • 2.3 Vuex插件
  • 3. Pinia基础
    • 3.1 Pinia介绍
    • 3.2 安装和基本设置
    • 3.3 定义Store
    • 3.4 使用Store
    • 3.5 修改状态
      • 3.5.1 直接修改
      • 3.5.2 使用actions
      • 3.5.3 使用$patch方法
      • 3.5.4 替换整个状态
    • 3.6 Pinia插件
  • 4. Vuex与Pinia的对比
    • 4.1 核心概念对比
    • 4.2 API风格对比
      • 4.2.1 Store定义
      • 4.2.2 在组件中使用
    • 4.3 优缺点比较
      • 4.3.1 Vuex优缺点
      • 4.3.2 Pinia优缺点
  • 5. 高级状态管理模式
    • 5.1 组合式Store模式
      • 5.1.1 Vuex中的组合
      • 5.1.2 Pinia中的组合
    • 5.2 持久化状态
      • 5.2.1 Vuex持久化
      • 5.2.2 Pinia持久化
    • 5.3 状态重置
      • 5.3.1 Vuex状态重置
      • 5.3.2 Pinia状态重置
    • 5.4 状态订阅
      • 5.4.1 Vuex状态订阅
      • 5.4.2 Pinia状态订阅

正文

1. 状态管理概述

1.1 什么是状态管理

在Vue应用中,状态管理是指对应用中各种数据状态的集中式管理。随着应用规模的增长,组件之间共享状态变得越来越复杂,简单的父子组件通信方式可能不再适用。状态管理工具提供了一种集中式存储管理应用所有组件的状态的方式。

1.2 为什么需要状态管理

当应用变得复杂时,我们面临以下挑战:

  • 多个视图依赖同一状态
  • 来自不同视图的行为需要变更同一状态
  • 组件层级深,组件间通信变得困难
  • 全局状态难以追踪变化来源

状态管理工具解决了这些问题,提供了:

  • 集中存储共享状态
  • 可预测的状态变更机制
  • 开发工具支持的调试能力
  • 模块化的状态管理方案

2. Vuex基础

2.1 Vuex核心概念

Vuex是Vue官方的状态管理模式,它采用集中式存储管理应用的所有组件的状态。

2.1.1 State

State是存储应用状态的地方:

// store/index.js
import { createStore } from 'vuex'export default createStore({state: {count: 0,todos: [],user: null}
})

在组件中访问状态:

// Vue 2
export default {computed: {count() {return this.$store.state.count}}
}// Vue 3
import { computed } from 'vue'
import { useStore } from 'vuex'export default {setup() {const store = useStore()const count = computed(() => store.state.count)return { count }}
}

2.1.2 Getters

Getters用于从store的state中派生出一些状态:

// store/index.js
export default createStore({state: {todos: [{ id: 1, text: 'Learn Vue', done: true },{ id: 2, text: 'Learn Vuex', done: false }]},getters: {doneTodos: state => {return state.todos.filter(todo => todo.done)},doneTodosCount: (state, getters) => {return getters.doneTodos.length}}
})

在组件中使用getters:

// Vue 2
export default {computed: {doneTodosCount() {return this.$store.getters.doneTodosCount}}
}// Vue 3
import { computed } from 'vue'
import { useStore } from 'vuex'export default {setup() {const store = useStore()const doneTodosCount = computed(() => store.getters.doneTodosCount)return { doneTodosCount }}
}

2.1.3 Mutations

Mutations是更改store中状态的唯一方法:

// store/index.js
export default createStore({state: {count: 0},mutations: {increment(state) {state.count++},incrementBy(state, payload) {state.count += payload.amount}}
})

在组件中提交mutations:

// Vue 2
export default {methods: {increment() {this.$store.commit('increment')},incrementBy(amount) {this.$store.commit('incrementBy', { amount })}}
}// Vue 3
import { useStore } from 'vuex'export default {setup() {const store = useStore()function increment() {store.commit('increment')}function incrementBy(amount) {store.commit('incrementBy', { amount })}return { increment, incrementBy }}
}

2.1.4 Actions

Actions用于处理异步操作:

// store/index.js
export default createStore({state: {todos: []},mutations: {setTodos(state, todos) {state.todos = todos},addTodo(state, todo) {state.todos.push(todo)}},actions: {async fetchTodos({ commit }) {try {const response = await fetch('/api/todos')const todos = await response.json()commit('setTodos', todos)} catch (error) {console.error('Error fetching todos:', error)}},async addTodo({ commit }, todoText) {try {const response = await fetch('/api/todos', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ text: todoText, done: false })})const newTodo = await response.json()commit('addTodo', newTodo)} catch (error) {console.error('Error adding todo:', error)}}}
})

在组件中分发actions:

// Vue 2
export default {created() {this.$store.dispatch('fetchTodos')},methods: {addTodo() {this.$store.dispatch('addTodo', this.newTodoText)}}
}// Vue 3
import { useStore } from 'vuex'
import { ref, onMounted } from 'vue'export default {setup() {const store = useStore()const newTodoText = ref('')onMounted(() => {store.dispatch('fetchTodos')})function addTodo() {store.dispatch('addTodo', newTodoText.value)newTodoText.value = ''}return { newTodoText, addTodo }}
}

2.1.5 Modules

Modules用于将store分割成模块:

// store/modules/counter.js
export default {namespaced: true,state: {count: 0},getters: {doubleCount: state => state.count * 2},mutations: {increment(state) {state.count++}},actions: {incrementAsync({ commit }) {setTimeout(() => {commit('increment')}, 1000)}}
}// store/modules/todos.js
export default {namespaced: true,state: {list: []},getters: {doneTodos: state => state.list.filter(todo => todo.done)},mutations: {add(state, todo) {state.list.push(todo)}},actions: {async fetchAll({ commit }) {const response = await fetch('/api/todos')const todos = await response.json()todos.forEach(todo => commit('add', todo))}}
}// store/index.js
import { createStore } from 'vuex'
import counter from './modules/counter'
import todos from './modules/todos'export default createStore({modules: {counter,todos}
})

在组件中访问模块:

// Vue 2
export default {computed: {count() {return this.$store.state.counter.count},doubleCount() {return this.$store.getters['counter/doubleCount']}},methods: {increment() {this.$store.commit('counter/increment')},incrementAsync() {this.$store.dispatch('counter/incrementAsync')}}
}// Vue 3
import { computed } from 'vue'
import { useStore } from 'vuex'export default {setup() {const store = useStore()const count = computed(() => store.state.counter.count)const doubleCount = computed(() => store.getters['counter/doubleCount'])function increment() {store.commit('counter/increment')}function incrementAsync() {store.dispatch('counter/incrementAsync')}return { count, doubleCount, increment, incrementAsync }}
}

2.2 Vuex辅助函数

Vuex提供了一些辅助函数简化代码:

2.2.1 mapState

// Vue 2
import { mapState } from 'vuex'export default {computed: {// 映射this.count为store.state.count...mapState({count: state => state.count,countAlias: 'count',countPlusLocalState(state) {return state.count + this.localCount}}),// 映射this.count为store.state.count...mapState(['count', 'todos'])}
}

2.2.2 mapGetters

// Vue 2
import { mapGetters } from 'vuex'export default {computed: {// 映射this.doneTodosCount为store.getters.doneTodosCount...mapGetters(['doneTodosCount','anotherGetter',]),// 使用别名...mapGetters({doneCount: 'doneTodosCount'})}
}

2.2.3 mapMutations

// Vue 2
import { mapMutations } from 'vuex'export default {methods: {// 映射this.increment()为this.$store.commit('increment')...mapMutations(['increment','incrementBy']),// 使用别名...mapMutations({add: 'increment'})}
}

2.2.4 mapActions

// Vue 2
import { mapActions } from 'vuex'export default {methods: {// 映射this.fetchTodos()为this.$store.dispatch('fetchTodos')...mapActions(['fetchTodos','addTodo']),// 使用别名...mapActions({fetch: 'fetchTodos'})}
}

2.2.5 createNamespacedHelpers

// Vue 2
import { createNamespacedHelpers } from 'vuex'// 创建基于某个命名空间辅助函数
const { mapState, mapActions } = createNamespacedHelpers('counter')export default {computed: {// 映射this.count为store.state.counter.count...mapState({count: state => state.count})},methods: {// 映射this.increment()为this.$store.dispatch('counter/increment')...mapActions(['increment','incrementAsync'])}
}

2.3 Vuex插件

Vuex支持插件系统,用于扩展功能:

// 简单的日志插件
const myPlugin = store => {// 当store初始化后调用store.subscribe((mutation, state) => {// 每次mutation之后调用console.log('mutation type:', mutation.type)console.log('mutation payload:', mutation.payload)console.log('current state:', state)})
}// 持久化状态插件
const persistStatePlugin = store => {// 从localStorage恢复状态const savedState = localStorage.getItem('vuex-state')if (savedState) {store.replaceState(JSON.parse(savedState))}// 订阅状态变更store.subscribe((mutation, state) => {localStorage.setItem('vuex-state', JSON.stringify(state))})
}// 在store中使用插件
export default createStore({// ...plugins: [myPlugin, persistStatePlugin]
})

3. Pinia基础

3.1 Pinia介绍

Pinia是Vue官方团队成员开发的新一代状态管理库,被视为Vuex 5的替代品。它提供了更简单的API、完整的TypeScript支持,以及更好的开发体验。

3.2 安装和基本设置

# 安装Pinia
npm install pinia
# 或
yarn add pinia

在Vue应用中注册Pinia:

// main.js (Vue 3)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const app = createApp(App)
app.use(createPinia())
app.mount('#app')

3.3 定义Store

在Pinia中,store的定义更加直观:

// stores/counter.js
import { defineStore } from 'pinia'// 第一个参数是store的唯一ID
export const useCounterStore = defineStore('counter', {// state是一个返回初始状态的函数state: () => ({count: 0,name: 'Eduardo'}),// getters类似于组件的计算属性getters: {doubleCount: (state) => state.count * 2,// 使用this访问其他gettersdoubleCountPlusOne() {return this.doubleCount + 1}},// actions包含可以修改状态和执行异步操作的方法actions: {increment() {this.count++},async fetchData() {const response = await fetch('/api/data')const data = await response.json()this.count = data.count}}
})

3.4 使用Store

在组件中使用Pinia store:

// Vue 3 组件
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'// 获取store实例
const counterStore = useCounterStore()// 解构store状态和getters (使用storeToRefs保持响应性)
const { count, name } = storeToRefs(counterStore)
const { doubleCount } = storeToRefs(counterStore)// 直接调用actions
function handleClick() {counterStore.increment()
}// 异步actions
async function loadData() {await counterStore.fetchData()
}
</script><template><div><p>Count: {{ count }}</p><p>Double count: {{ doubleCount }}</p><p>Name: {{ name }}</p><button @click="handleClick">Increment</button><button @click="loadData">Load Data</button></div>
</template>

3.5 修改状态

Pinia提供了多种修改状态的方式:

3.5.1 直接修改

const store = useCounterStore()// 直接修改状态
store.count++
store.name = 'Alex'

3.5.2 使用actions

const store = useCounterStore()// 通过actions修改
store.increment()

3.5.3 使用$patch方法

const store = useCounterStore()// 一次性修改多个状态
store.$patch({count: store.count + 1,name: 'Alex'
})// 使用函数形式的$patch处理复杂状态
store.$patch((state) => {state.count++state.name = 'Alex'// 可以包含更复杂的逻辑if (state.items) {state.items.push({ id: nanoid(), text: 'New Item' })}
})

3.5.4 替换整个状态

const store = useCounterStore()// 完全替换状态
store.$state = {count: 10,name: 'Alex'
}

3.6 Pinia插件

Pinia支持插件系统,可以扩展store功能:

// plugins/persistPlugin.js
import { toRaw } from 'vue'export function persistPlugin({ store }) {// 从localStorage恢复状态const storedState = localStorage.getItem(`pinia-${store.$id}`)if (storedState) {store.$state = JSON.parse(storedState)}// 监听状态变化并保存store.$subscribe((mutation, state) => {localStorage.setItem(`pinia-${store.$id}`, JSON.stringify(toRaw(state)))})
}// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { persistPlugin } from './plugins/persistPlugin'
import App from './App.vue'const pinia = createPinia()
pinia.use(persistPlugin)const app = createApp(App)
app.use(pinia)
app.mount('#app')

4. Vuex与Pinia的对比

4.1 核心概念对比

特性VuexPinia
状态定义state对象state函数
计算状态gettersgetters
同步修改mutationsactions
异步操作actionsactions
模块化modules + namespaced多个store
TypeScript支持有限完全支持
开发工具Vue DevtoolsVue Devtools

4.2 API风格对比

4.2.1 Store定义

Vuex:

// store/index.js
import { createStore } from 'vuex'export default createStore({state: {count: 0},getters: {doubleCount: state => state.count * 2},mutations: {increment(state) {state.count++}},actions: {incrementAsync({ commit }) {setTimeout(() => {commit('increment')}, 1000)}}
})

Pinia:

// stores/counter.js
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),getters: {doubleCount: state => state.count * 2},actions: {increment() {this.count++},incrementAsync() {setTimeout(() => {this.increment()}, 1000)}}
})

4.2.2 在组件中使用

Vuex (Vue 2):

import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'export default {computed: {...mapState(['count']),...mapGetters(['doubleCount'])},methods: {...mapMutations(['increment']),...mapActions(['incrementAsync'])}
}

Vuex (Vue 3 Composition API):

import { computed } from 'vue'
import { useStore } from 'vuex'export default {setup() {const store = useStore()const count = computed(() => store.state.count)const doubleCount = computed(() => store.getters.doubleCount)function increment() {store.commit('increment')}function incrementAsync() {store.dispatch('incrementAsync')}return { count, doubleCount, increment, incrementAsync }}
}

Pinia:

import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'export default {setup() {const store = useCounterStore()// 解构时保持响应性const { count } = storeToRefs(store)const { doubleCount } = storeToRefs(store)// 直接使用actionsconst { increment, incrementAsync } = storereturn { count, doubleCount, increment, incrementAsync }}
}

4.3 优缺点比较

4.3.1 Vuex优缺点

优点:

  • 成熟稳定,生态系统丰富
  • 与Vue 2完全兼容
  • 严格的状态管理模式,mutation/action分离
  • 丰富的中间件和插件

缺点:

  • API相对复杂,样板代码较多
  • TypeScript支持有限
  • 模块嵌套时命名空间管理复杂
  • 必须通过mutation修改状态

4.3.2 Pinia优缺点

优点:

  • API简洁,样板代码少
  • 完全支持TypeScript
  • 更好的开发体验和IDE支持
  • 可直接修改状态,无需mutations
  • 更小的包体积
  • 模块化设计更简单,无需嵌套

缺点:

  • 相对较新,生态系统不如Vuex丰富
  • 对Vue 2需要额外适配
  • 缺少严格模式的状态追踪

5. 高级状态管理模式

5.1 组合式Store模式

5.1.1 Vuex中的组合

// store/modules/user.js
export default {namespaced: true,state: () => ({profile: null,preferences: {}}),getters: {isLoggedIn: state => !!state.profile},mutations: {setProfile(state, profile) {state.profile = profile}},actions: {async login({ commit }, credentials) {const profile = await authService.login(credentials)commit('setProfile', profile)return profile}}
}// store/modules/cart.js
export default {namespaced: true,state: () => ({items: []}),getters: {totalPrice: state => {return state.items.reduce((total, item) => {return total + (item.price * item.quantity)}, 0)}},mutations: {addItem(state, item) {state.items.push(item)}},actions: {async checkout({ state, commit }) {await orderService.createOrder(state.items)// 清空购物车state.items = []}}
}// store/index.js
import { createStore } from 'vuex'
import user from './modules/user'
import cart from './modules/cart'export default createStore({modules: {user,cart}
})

5.1.2 Pinia中的组合

// stores/user.js
import { defineStore } from 'pinia'
import { authService } from '@/services/auth'export const useUserStore = defineStore('user', {state: () => ({profile: null,preferences: {}}),getters: {isLoggedIn: state => !!state.profile},actions: {async login(credentials) {const profile = await authService.login(credentials)this.profile = profilereturn profile},logout() {this.profile = null}}
})// stores/cart.js
import { defineStore } from 'pinia'
import { orderService } from '@/services/order'export const useCartStore = defineStore('cart', {state: () => ({items: []}),getters: {totalPrice: state => {return state.items.reduce((total, item) => {return total + (item.price * item.quantity)}, 0)}},actions: {addItem(item) {this.items.push(item)},async checkout() {await orderService.createOrder(this.items)this.items = []}}
})// 在组件中组合使用多个store
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'export default {setup() {const userStore = useUserStore()const cartStore = useCartStore()async function checkoutAsUser() {if (!userStore.isLoggedIn) {await userStore.login()}await cartStore.checkout()}return { userStore, cartStore, checkoutAsUser }}
}

5.2 持久化状态

5.2.1 Vuex持久化

使用vuex-persistedstate插件:

// store/index.js
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'export default createStore({// ...store配置plugins: [createPersistedState({key: 'vuex-state',paths: ['user.profile', 'cart.items'] // 只持久化特定路径})]
})

5.2.2 Pinia持久化

使用pinia-plugin-persistedstate插件:

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)const app = createApp(App)
app.use(pinia)
app.mount('#app')// stores/user.js
import { defineStore } from 'pinia'export const useUserStore = defineStore('user', {state: () => ({profile: null}),// 配置持久化persist: {key: 'user-store',storage: localStorage,paths: ['profile']},// ...其他配置
})

5.3 状态重置

5.3.1 Vuex状态重置

// store/index.js
export default createStore({// ...mutations: {// 添加重置状态的mutationresetState(state) {Object.assign(state, initialState)}},actions: {resetStore({ commit }) {commit('resetState')}}
})// 在组件中使用
methods: {logout() {this.$store.dispatch('resetStore')}
}

5.3.2 Pinia状态重置

Pinia提供了内置的$reset方法:

// 在组件中使用
const store = useCounterStore()// 重置store到初始状态
function resetStore() {store.$reset()
}

5.4 状态订阅

5.4.1 Vuex状态订阅

// 订阅mutation
store.subscribe((mutation, state) => {console.log('mutation type:', mutation.type)console.log('mutation payload:', mutation.payload)console.log('current state:', state)
})// 订阅action
store.subscribeAction({before: (action, state) => {console.log(`before action ${action.type}`)},after: (action, state) => {console.log(`after action ${action.type}`)}
})

5.4.2 Pinia状态订阅

const store = useCounterStore()// 订阅状态变化
const unsubscribe = store.$subscribe((mutation, state) => {// 每次状态变化时触发console.log('mutation type:', mutation.type)console.log('mutation payload:', mutation.payload)console.log('current state:', state)
}, { detached: false }) // detached: true 会在组件卸载后保持订阅// 订阅action
store.$onAction(({name, // action名称store, // store实例args, // 传递给action的参数数组after, // 在action返回或解决后的钩子onError // action抛出或拒绝的钩子
}) => {console.log(`Start "${name}" with params [${args.join(', ')}]`)after((result) => {console.log(`Finished "${name}" with result: ${result}`)})onError((error) => {console.error(`Failed "${name}" with error: ${error}`)})
})

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述

相关文章:

  • 深度学习训练中的显存溢出问题分析与优化:以UNet图像去噪为例
  • yaml里的挪威问题是啥
  • day3 打卡训练营
  • 编程思想之分片
  • JavaScript 笔记 --- part 5 --- Web API (part 3)
  • 力扣hot100 LeetCode 热题 100 Java 哈希篇
  • CFD技术如何实现污水处理曝气池的设计优化和节能降碳?
  • vue中将elementUI和echarts转成pdf文件
  • LLM 论文精读(二)Training Compute-Optimal Large Language Models
  • 图像挖掘课程笔记-第一章:了解机器视觉
  • [大模型]什么是function calling?
  • IOT项目——双轴追光系统
  • 第六篇:linux之解压缩、软件管理
  • RS232借助 Profinet网关与调制解调器碰撞出的火花
  • AI 硬件定制:开启智能新时代的钥匙
  • Vue3 异步组件详解:从原理到实战的保姆级指南
  • 【OSG源码阅读】Day 2: 初始化流程
  • Megatron - LM 重要文件解析 - /tools/preprocess_data.py
  • 极狐GitLab 中如何自定义角色?
  • windows使用openssl生成IIS自签证书全流程
  • 今年五一假期出游人群规模预计比去年提升8%,哪里最热门?
  • 美国务院宣布新一轮与伊朗相关的制裁
  • 新华时评:防范安全事故须臾不可放松
  • 强制性国家标准《危险化学品企业安全生产标准化通用规范》发布
  • “上博号”彩绘大飞机今日启航:万米高空传播中国古代文化
  • 住房和城乡建设部办公厅主任李晓龙已任部总工程师