跳转到内容

Pinia 状态管理与持久化

快速开始

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

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); // 使用持久化插件

app.use(pinia);

store

定义方式

js
// stores/modules/toc.store.js
import { defineStore } from 'pinia';

export const useTocStore = defineStore('toc', {
	state: () => ({
		tocList: [],
		isVisible: true,
		currentRoutePath: '',
	}),
	getters: {
		shouldShowToc() {
			return this.tocList.length > 0 || this.isLoading;
		},
	},
	actions: {
		addTocItem(item) {
			if (!this.tocList.find((i) => i.id === item.id)) {
				this.tocList.push(item);
			}
		},
		clearToc() {
			this.tocList = [];
		},
	},
	// 持久化配置 - 4.3.0版本的最新配置方式
	persist: [
		{
			key: 'toc-store',
			storage: localStorage,
			pick: ['isVisible'], // 只持久化特定字段
		},
	],
});
js
// stores/modules/theme.store.js
import { defineStore } from 'pinia';
import { reactive } from 'vue';

export const useThemeStore = defineStore(
	'theme',
	() => {
		// 状态定义
		const themeConfig = reactive({
			layout: 'centerLayout',
			theme: 'redTheme',
			fontSize: 'normal',
		});

		// actions
		const updateLayout = (layout) => {
			themeConfig.layout = layout;
		};

		const updateTheme = (theme) => {
			themeConfig.theme = theme;
			// 更新DOM中的主题类
			const body = document.body;
			if (theme === 'blueTheme') {
				body.classList.add('theme-blue');
				body.classList.remove('theme-default');
			} else {
				body.classList.add('theme-default');
				body.classList.remove('theme-blue');
			}
		};

		return {
			themeConfig,
			updateLayout,
			updateTheme,
		};
	},
	{
		// 持久化配置
		persist: {
			key: 'vue-vite-theme',
			storage: localStorage,
		},
	}
);

使用方法

一、在组件中使用

vue
<template>
	<div>
		<p>当前主题: {{ themeStore.themeConfig.theme }}</p>
		<button @click="changeTheme">切换主题</button>
	</div>
</template>
<script setup>
import { useThemeStore } from '@/stores/modules/theme.store';

const themeStore = useThemeStore();

function changeTheme() {
	const newTheme = themeStore.themeConfig.theme === 'redTheme' ? 'blueTheme' : 'redTheme';
	themeStore.updateTheme(newTheme);
}
</script>

二、解构

使用  storeToRefs  可以保持响应性的同时解构 store 属性

vue
<script setup>
import { useThemeStore } from '@/stores/modules/theme.store';
import { storeToRefs } from 'pinia';

const themeStore = useThemeStore();
// 使用 storeToRefs 保持响应性
const { themeConfig } = storeToRefs(themeStore);
// actions 可以直接解构
const { updateTheme } = themeStore;

function changeTheme() {
	const newTheme = themeConfig.value.theme === 'redTheme' ? 'blueTheme' : 'redTheme';
	updateTheme(newTheme);
}
</script>

三、相互调用

js
import { defineStore } from 'pinia';
import { useThemeStore } from './theme.store';

export const useUserStore = defineStore('user', {
	state: () => ({
		preferences: {
			theme: 'default',
		},
	}),
	actions: {
		// 在一个 store 中调用另一个 store 的 action
		applyUserPreferences() {
			const themeStore = useThemeStore();
			themeStore.updateTheme(this.preferences.theme);
		},
	},
});

持久化

配置

一、安装

shell
npm i pinia-plugin-persistedstate

二、配置

js
export const useCounterStore = defineStore("counter", {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++;
    }
  },
  persist: true // 最简单的配置,持久化所有状态
});
js
export const useUserStore = defineStore(
  "user",
  () => {
    const user = ref(null);
    const setUser = newUser => {
      user.value = newUser;
    };
    return { user, setUser };
  },
  {
    persist: true // 最简单的配置,持久化所有状态
  }
);

自定义持久化字段

警告

pinia-plugin-persistedstate 4.3.0 版本中,选择要持久化的字段应使用  pick  而不是  paths。 使用错误的属性名可能导致整个 state 被持久化,而不是只持久化指定字段。

js
export const useAdvancedStore = defineStore("advanced", {
  state: () => ({
    user: null,
    token: null,
    settings: {
      theme: "light",
      notifications: true
    },
    tempData: [] // 临时数据,不需要持久化
  }),
  persist: {
    // 自定义存储键名
    key: "my-custom-key",

    // 使用 sessionStorage 而非默认的 localStorage
    storage: sessionStorage,

    // 只持久化特定字段的数据(4.3.0版本使用pick而不是paths)
    pick: ["user", "token", "settings.theme"],

    // 自定义序列化/反序列化
    serializer: {
      deserialize: JSON.parse,
      serialize: JSON.stringify
    },

    // 持久化前的数据转换
    beforeRestore: context => {
      console.log("即将恢复数据", context);
    },

    // 持久化后的回调
    afterRestore: context => {
      console.log("数据已恢复", context);
    }
  }
});

多策略

可以为不同的数据配置不同的持久化策略,多策略配置使用数组形式直接定义,而不是通过  strategies  属性

js
export const useMultiStore = defineStore("multi", {
  state: () => ({
    user: null,
    token: null,
    preferences: {
      theme: "light",
      fontSize: "medium"
    },
    cachedData: {}
  }),
  // 4.3.0版本使用数组形式定义多策略
  persist: [
    // 敏感数据使用 sessionStorage,关闭浏览器后清除
    {
      key: "auth-data",
      storage: sessionStorage,
      pick: ["token", "user"]
    },
    // 用户偏好使用 localStorage 长期保存
    {
      key: "user-preferences",
      storage: localStorage,
      pick: ["preferences"]
    },
    // 缓存数据使用自定义存储
    {
      key: "cached-data",
      storage: {
        getItem: key => {
          // 从 IndexedDB 或其他存储获取数据
          return localStorage.getItem(key);
        },
        setItem: (key, value) => {
          // 存储到 IndexedDB 或其他存储
          localStorage.setItem(key, value);
        }
      },
      pick: ["cachedData"]
    }
  ]
});

模块化

js
// stores/index.js
const modules = import.meta.glob('./modules/*.store.js', { eager: true });

// 将所有导出的 useXXXStore 统一导出
const stores = {};

for (const path in modules) {
	const mod = modules[path];
	for (const key in mod) {
		stores[key] = mod[key];
	}
}

export default stores;

实践

重置

javascript
// 在组件中重置 store 状态
const userStore = useUserStore();

function logout() {
	userStore.$reset(); // 重置为初始状态
	router.push('/login');
}

批量更新

javascript
const userStore = useUserStore();

// 批量更新多个状态
userStore.$patch({
	name: 'John',
	age: 30,
	preferences: {
		theme: 'dark',
	},
});

// 或者使用函数形式进行更复杂的更新
userStore.$patch((state) => {
	state.name = 'John';
	state.age = 30;
	state.lastLogin = new Date();
	state.loginCount++;
});

订阅状态变化

javascript
const userStore = useUserStore();

// 订阅整个 store 的变化
const unsubscribe = userStore.$subscribe(
	(mutation, state) => {
		// 每次状态变化时触发
		console.log('状态变化:', mutation);
		console.log('当前状态:', state);

		// 可以在这里执行额外的操作,如同步到服务器
		saveToLocalStorage('user-state', state);
	},
	{ detached: true }
); // detached: true 表示组件卸载后依然保持订阅

// 在需要时取消订阅
onUnmounted(() => {
	unsubscribe();
});

Will Try My Best.