Skip to content

vue 状态管理

1. 简介

1.1 为什么要有状态管理

  • 解决组件间共享状态的复杂性 多个组件或嵌套层级较深的组件需要共享同一状态
  • 避免数据流混乱与不一致性 分散的状态可能导致同一数据在不同组件中表现不一致,状态管理通过‌集中存储‌确保数据来源唯一性,降低数据冲突风险‌
  • ‌提升代码可维护性与可预测性 集中化状态管理使数据变更路径清晰,便于代码维护与调试
  • 支持异步操作与复杂逻辑处理 状态管理库(如 Vuex、Pinia)提供标准化异步操作处理(如 actions),避免直接在组件中耦合异步代码,提升逻辑复用性‌

总结: 为集中化管理数据,封装的轮子

1.2 状态管理提供什么功能

  • 集中化存储 通过全局唯一的 store 统一管理应用核心状态

  • 获取状态 实时获取 store 中的状态,支持响应式更新

  • 状态变更 支持同步(mutations)和异步( actions)来修改 store状态

  • 状态持久化与恢复 通过插件,支持从本地存储恢复状态

1.3 状态管理库的选择

VuexPinia 未来趋势
Vue 2 官方推荐状态管理库Vue 3 官方推荐状态管理库
支持‌‌TypeScript,但需额外配置‌原生支持‌‌TypeScript‌,类型推断更完善

2. Vuex

js
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 引入模块
import animal from './animal'

export default new Vuex.Store({
  /**
   * 需要共享的状态
   * 
   * vue2使用:this.$store.state[属性]
   */
  state: { 
    count: 0,
    name: '张三'
  },
  /**
   * state 中状态不能直接修改
   * 使用 mutations 来修改状态
   * 
   * vue2使用:this.$store.commit('setCount', 2)
   */
  mutations: {
    /**
     * @param {*} state 第一个参数是 Store 中的状态(必传)
     * @param {*} newVal 第二个参数是传入的参数 (可选)
     */
    setCount (state, newVal) { 
      state.count = newVal
    }
    setName (state, newVal) {
      state.name = newVal
    }
  },
  /**
   * 和 mutations 类似,用来修改状态
   * 区别: 异步处理,异步任务必须要使用 actions
   * 
   * vue2使用:this.$store.dispatch('changeNameAsync', '李四')
   */
  actions: {
    /**
     * @param {*} context 上下文对象,包含 commit 方法
     * @param {*} newVal 传入的参数
     */
    changeNameAsync (context, newVal) {
      setTimeout(() => {
        // 在这里调用 mutations 中的处理方法
        context.commit('setName', newVal)
      }, 2000)
    }
  },
  /**
   * 相当于计算属性
   * 用来封装计算逻辑,有缓存的功能
   * 只有当它的依赖状态发生改变,才会重新计算
   * 
   * vue2使用:this.$store.getters.[属性]
   */
  getters: { 
    /**
     * @param {*} state 状态
     * @returns {*} 返回值
     */
    doubleCount: (state) => {
      return state.count * 2
    }
  },
  /**
   * 避免在一个复杂的项目 state 中的数据变得臃肿,Vuex 允许将 Store 分成不同的模块
   * 每个模块可以包含自己的 state、mutations、actions、getters
   * 
   * vue2使用:this.$store.state.animal.[属性] 获取状态
   * vue2使用:this.$store.commit('animal/setName', '李四') 修改状态
   * vue2使用:this.$store.dispatch('animal/changeNameAsync', '李四') 异步修改状态
   * vue2使用:this.$store.getters.animal.doubleCount 获取计算属性
   */
  modules: {
    // 模块
    animal,
  }
});

3. Pinia

3.1 创建入口文件

js
// store/index.js
import { createPinia } from 'pinia'
const pinia = createPinia();
export default pinia;

3.2 main.js引入

js
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';

const app = createApp(App);

app.use(store);
app.mount('#app');

3.3 创建状态

  • 支持组合式和选项式
  • 推荐选项式
js
// store/useCounter.js
import { defineStore } from 'pinia'
/**
 * 定义一个 store
 *
 * @param {string} counter 唯一标识,必须是唯一的
 * @param {object} options 配置选项
 */
const useCounter = defineStore('counter', {
  /**
   * 定义状态
   */
  state: () => ({ 
    count: 0,
    name: '张三'
  }),
  /**
   * 定义修改状态的方法
   */
  actions: {
    /**
     * 修改state状态
     * @param {*} newVal 传入的参数
     */
    setCount(newVal) { 
      this.count = newVal
    }, 
    /**
     * 异步修改state状态
     * @param {*} newVal 传入的参数
     */
    async setCountAsync(newVal) { 
      this.setCount(newVal) 
    },
  },
  /**
   * 定义计算属性
   */
  getters: { 
    doubleCount (state) => {
      return state.count * 2
    },
  }
});

export default useCounter;

3.4 组件中使用

vue
<script setup>
import { storeToRefs } from 'pinia';
import { useCounter } from './store/useCounter';
const storeCounter = useCounter();

// 通过pinia自带的方法,转换成ref,就是响应式的了
let {count, name} = storeToRefs(storeCounter)

// 获取值
console.log(storeCounter.count); // 0

// 修改值1 - 通过actions
storeCounter.setCount(1);

// 修改值2 - 直接修改
storeCounter.count = 2;

// 修改值3 - 通过patch 
storeCounter.$patch({
  count: 3,
  name: '李四'
})
</script>

3.5 持久化

可以自己存储,建议使用插件

pinia-plugin-persistedstate 是一个 Pinia 插件,用于实现状态的持久化存储。它可以将 Pinia 中的状态存储到本地存储(如 localStorage、sessionStorage)中,使得状态在页面刷新或关闭后仍然保持不变。

3.5.1 安装插件

bash
npm install pinia-plugin-persistedstate

3.5.2 引入插件

js
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

const app = createApp(App)

app.use(pinia)
app.mount('#app')

3.5.3 使用

创建 Store 时,将 persist 选项设置为 true。

js
import { defineStore } from 'pinia'

export const useStore = defineStore(
  'counter',
  () => {
    const count = ref(0)
    function increment() {
      count.value++
    }

    return { count, increment }
  },
  {
    persist: true
  }
)

3.5.4 配置

默认配置

  • 使用 localStorage 进行存储
  • store.$id 作为 storage 默认的 key
  • 使用 JSON.stringify/JSON.parse 进行序列化/反序列化
  • 整个 state 默认将被持久化

京ICP备2024093538号-1