Pinia入门
- localStorage:永久本地存储(关闭浏览器还在),存 token 首选(你项目当前方案)
- sessionStorage:浏览器标签关闭失效
- Cookie :自动随请求带到后端、有过期时间,一般存少量鉴权字段,不做 Pinia 状态持久化首选
Pinia只存内存,刷新页面全部清空,官方没内置 localStorage/cookie 自动存储逻辑,必须二选一:
插件自动封装(主流:pinia-plugin-persistedstate),一行开启自动持久化,不用手动写任何存 localStorage 代码
java
// user.ts只用加 persist:true,插件自动监听state变化→自动存localStorage,刷新自动回填token/userInfo
export const useUserStore = defineStore('app-user',{
state:()=>({token:undefined,userInfo:null}),
persist:true, // 开启自动持久化
actions:{
setToken(info){
this.token = info
// 不用再写 setAuthCache!插件自动同步localStorage
}
}
})
Pinia 是 Vue 官方推荐的新一代状态管理库 ,替代 Vuex,核心优势:简洁、TS 友好、模块化、无需 mutations、支持组合式 API。
npm install pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia()) // 注册 Pinia
app.mount('#app')
定义 Store(模块化)
java
// 1. 导入定义 Store 的方法
import { defineStore } from 'pinia'
// 2. 定义 Store(唯一ID + 配置)
export const useUserStore = defineStore('user', {
// 状态数据(等价于 Vuex state)
state: () => ({
name: '张三',
age: 18,
token: ''
}),
// 计算属性(等价于 Vuex getters)
getters: {
// 自动接收 state 作为参数
doubleAge: (state) => state.age * 2,
// 也可以用 this 访问整个 store
getName() {
return `我的名字:${this.name}`
}
},
// 方法(等价于 Vuex actions + mutations,直接修改 state)
actions: {
updateName(name) {
this.name = name // 直接修改,不需要 mutation
},
updateAge(age) {
this.age = age
},
// 支持异步
async login() {
const res = await fetch('/api/login')
const data = await res.json()
this.token = data.token
}
}
})
html
<template>
<div>
<p>名字:{{ userStore.name }}</p>
<p>年龄:{{ userStore.age }}</p>
<p>双倍年龄:{{ userStore.doubleAge }}</p>
<button @click="userStore.updateName('李四')">修改名字</button>
</div>
</template>
<script setup>
// 导入 Store
import { useUserStore } from '@/stores/user'
// 拿到 Store 实例
const userStore = useUserStore()
</script>
解构状态(保持响应式)
直接解构会丢失响应式,必须用 storeToRefs:
html
import { storeToRefs } from 'pinia'
// 正确:响应式解构
const { name, age, doubleAge } = storeToRefs(userStore)
// 方法直接解构
const { updateName } = userStore
小程序页面刷新 / 切换不会丢失状态,但冷启动会重置 必须做本地持久化 (如存入 uni.setStorageSync)
npm install pinia-plugin-persistedstate
java
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import persistedstate from 'pinia-plugin-persistedstate' // 持久化
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
pinia.use(persistedstate) // 注册持久化
app.use(pinia)
return { app }
}
java
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
userInfo: null
}),
// 小程序必须加:持久化到本地存储
persist: {
// 存储 key
key: 'user-store',
// 使用 uniapp 存储 API
storage: {
getItem: uni.getStorageSync,
setItem: uni.setStorageSync
}
}
})
项目开始
入口
let app: Nullable<App<Element>> = null;
let store: Nullable<Pinia> = null;
export function setupStore($app: App<Element>) {
if (store == null) {
store = createPinia();
}
app = $app;
app.use(store);
}
类型很严格
项目中的 store 模块位于 src/store/modules/'

看userStore
存储用户登录后的 token(身份凭证)
存储 用户基本信息(头像、昵称等)
存储 角色权限列表(用于菜单 / 按钮权限控制)
提供 登录、设置 token 等方法
支持在组件外使用
java
import { defineStore } from 'pinia';
import { store } from '/@/store';
///规定用户状态的数据结构
interface UserState {
userInfo: Nullable<UserInfo>;
token?: string;
roleList: RoleEnum[];
// ...
}
///创建 Store:defineStore
export const useUserStore = defineStore({
id: 'app-user', // 唯一标识
state: (): UserState => ({
// 用户信息
userInfo: null,
// token
token: undefined,
// 角色列表
roleList: [],
// 字典
dictItems: null,
// session过期时间
sessionTimeout: false,
// Last fetch time
lastUpdateTime: 0,
//租户id
tenantid: '',
// 分享租户ID
// 用于分享页面所属租户与当前用户登录租户不一致的情况
shareTenantId: null,
//登录返回信息
loginInfo: null,
}),
///计算属性(读取数据的安全方式)
getters: {
getUserInfo(): UserInfo {
return this.userInfo || {};
},
getToken(): string {
return this.token || getAuthCache(TOKEN_KEY);
},
// ...
},
///操作数据的方法
actions: {
setToken(info: string | undefined) {
this.token = info;
setAuthCache(TOKEN_KEY, info);
},
async login(params) {
const data = await loginApi(params);
this.setToken(data.token);
return this.afterLoginAction();
},
// ...
},
});
// 在非组件中使用
export function useUserStoreWithOut() {
return useUserStore(store);
}
正常的 useUserStore() 只能在 Vue 组件 /setup 中使用。
但像这些地方不能用:
- 路由守卫
router.beforeEach - axios 请求拦截器
- 工具函数
- 普通 ts 文件
所以必须提供一个提前注入了根 store 的方法。
使用
java
import { useUserStore } from '/@/store/modules/user';
export default defineComponent({
setup() {
const userStore = useUserStore();
// 访问状态
console.log(userStore.token);
console.log(userStore.getUserInfo);
// 调用 action
const handleLogin = async () => {
await userStore.login({ username: 'admin', password: '123456' });
};
return { userStore, handleLogin };
},
});
在非组件中使用
java
import { useUserStoreWithOut } from '/@/store/modules/user';
// 路由守卫中使用
export function createPermissionGuard(router) {
const userStore = useUserStoreWithOut();
router.beforeEach(async (to, from, next) => {
const token = userStore.getToken;
if (!token) {
next('/login');
return;
}
// ...
});
}
模板使用
java
<template>
<div>
<span>用户名:{{ userStore.getUserInfo.name }}</span>
<button @click="userStore.logout()">退出登录</button>
</div>
</template>
<script setup>
import { useUserStore } from '/@/store/modules/user';
const userStore = useUserStore();
</script>
登录实现

<Button type="primary" size="large" block @click="handleLogin" :loading="loading">
{{ t('sys.login.loginButton') }}
</Button>
loginButton: '登录',

java
// user.ts:151-161
async login(params) {
const { goHome = true, mode, ...loginParams } = params;
// 1️⃣ 调用后端登录接口
const data = await loginApi(loginParams, mode);
const { token, userInfo } = data;
// 2️⃣ 保存 Token
this.setToken(token);
this.setTenant(userInfo.loginTenantId);
// 3️⃣ 登录后处理
return this.afterLoginAction(goHome, data);
}
java
// user.ts:95-98
setToken(info: string | undefined) {
this.token = info ? info : ''; // ① 保存到内存
setAuthCache(TOKEN_KEY, info); // ② 保存到 localStorage
}
// auth/index.ts:33-36
export function setAuthCache(key, value) {
const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
return fn(key, value, true); // 调用 localStorage.setItem()
}
/**
* 静态方法:设置本地缓存(localStorage + 内存缓存 双层存储)
* @param key - 缓存的 key(枚举类型,规范管理)
* @param value - 要缓存的值
* @param immediate - 是否立即同步到 localStorage,默认 false
*/
static setLocal(
key: LocalKeys,
value: LocalStore[LocalKeys],
immediate = false
): void {
// 1. 先把数据 存到【内存缓存 localMemory】(最快,页面读取优先走内存)
// toRaw(value):解除响应式包裹(Pinia/Vue 的响应式对象不能直接存,必须转原生对象)
localMemory.set(key, toRaw(value));
// 2. 如果 immediate = true → 立即把【整个内存缓存】同步写入 localStorage
// 不立即写:性能更好,等统一时机再批量存;立即写:关键数据(如token)立刻落地
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
}
后置处理
java
// user.ts:179-222
async afterLoginAction(goHome?: boolean, data?: any) {
if (!this.getToken) return null;
// 获取详细用户信息
const userInfo = await this.getUserInfoAction();
// 保存登录信息
await this.setLoginInfo({ ...data, isLogin: true });
// 跳转到首页
goHome && (await router.replace(userInfo?.homePath || PageEnum.BASE_HOME));
}
用户信息保持
java
// user.ts:249-274
async getUserInfoAction(): Promise<UserInfo | null> {
if (!this.getToken) return null;
// 调用后端获取用户信息
const { userInfo, sysAllDictItems } = await getUserInfo();
if (userInfo) {
// 保存角色列表
const roleList = roles.map((item) => item.value) as RoleEnum[];
this.setRoleList(roleList);
// 保存用户信息
this.setUserInfo(userInfo); // ← 重点
}
// 保存字典数据
if (sysAllDictItems) {
this.setAllDictItems(sysAllDictItems);
}
return userInfo;
}
页面刷新
│
▼
Vue 应用重新初始化 → Pinia 内存状态全部清空(token = undefined)
│
▼
路由守卫 router.beforeEach 开始执行
│
▼
判断当前页面是否需要登录权限
│
└──→ 调用 userStore.getToken 获取 token
│
└──→ getter 执行:return this.token || getAuthCache(TOKEN_KEY)
│
├── this.token → 刷新后为 undefined(内存丢失)
└── getAuthCache(TOKEN_KEY) → 从 localStorage 持久化缓存读取
│
▼
成功获取到之前保存的有效 token
│
▼
自动调用 userStore.setToken 将 token 重新存入 Pinia 内存
│
▼
执行获取用户信息 action → 请求接口 /sys/user/info
│
▼
获取用户信息、角色权限列表
│
▼
更新 userInfo、roleList 到 Pinia + 本地缓存
│
▼
登录状态完整恢复
│
▼
路由守卫放行 → 正常进入页面