前端通用 Pinia Token 全流程操作指南(常见常用版)
前端通用 Pinia Token 全流程操作指南(常见常用版)
本文梳理 Vue3 官方推荐状态管理库 Pinia 下的 Token 操作逻辑,剥离具体项目细节,聚焦「获取→存储→使用→过期→清除」的核心生命周期,结合 Pinia 「无 mutations、actions 直接异步、模块化更简单」的特性,每个步骤均标注「通用场景+Pinia 通用方案+注意事项」,可直接作为开发速查表。
前置说明:Pinia 与 Vuex 的核心区别(Token 操作相关)
| 特性 | Vuex | Pinia |
|---|---|---|
| 修改状态的方式 | 必须通过 commit 调用 mutations | actions 中可直接修改 state |
| 异步处理 | actions 处理异步,mutations 同步 | actions 可直接写 async/await |
| 模块化 | 需开启 namespaced: true 隔离 | 天然模块化,无需额外配置 |
| TypeScript 支持 | 配置复杂 | 原生支持,类型推导友好 |
一、第一步:登录成功获取并存储 Token
通用场景
用户通过账号密码/验证码等方式登录,后端验证通过后返回 Token,需将 Token 存入 Pinia(全局共享)和本地存储(刷新不丢)。
Pinia 通用方案
- 定义
user模块的 Pinia Store,包含token状态和login异步 action; - 在
loginaction 中调用登录接口,提取 Token; - 直接修改 state(无需 commit),同时存入本地存储(推荐 localStorage)。
代码示例
// 1. 定义 Pinia Store(src/stores/user.js)import{ defineStore }from'pinia'import{ loginAPI }from'@/api/user'// 假设已封装好登录接口exportconst useUserStore =defineStore('user',{// state:必须是函数返回对象(避免组件间共享状态)state:()=>({token: localStorage.getItem('token')||''// 初始化时从本地存储读取}),// actions:直接处理异步,可直接修改 stateactions:{asynclogin(loginForm){// 调用登录接口const res =awaitloginAPI(loginForm)// 从接口返回中提取 Token(具体字段看后端文档)const token = res.data.token // 直接修改 state(无需 commit!Pinia 核心优势)this.token = token // 同时存入本地存储 localStorage.setItem('token', token)}}})// 2. 组件中调用(Vue3 组合式 API 示例)<script setup>import{ useUserStore }from'@/stores/user'const userStore =useUserStore()consthandleLogin=async()=>{try{await userStore.login({username:'admin',password:'123456'})alert('登录成功')// 跳转到首页(需结合路由)}catch(err){alert('登录失败:'+(err.response?.data?.msg ||'未知错误'))}}</script>注意事项
- ❌ 不要忘记同时存 Pinia 和本地存储:Pinia 刷新页面会清空,本地存储保证刷新不丢;
- ❌ 不要用 Vuex 的思维写 Pinia:无需
commit,直接this.token = token即可; - ❌ state 必须是函数返回对象:避免多个组件实例共享同一个 state 对象。
二、第二步:请求接口时自动携带 Token
通用场景
每次发起业务请求时,自动从 Pinia Store 中读取 Token 并添加到请求头。
Pinia 通用方案
- 在 Axios/Fetch 封装的请求拦截器中读取 Pinia Store;
- 注意:在 Vue 组件外使用 Pinia Store 时,需确保
app.use(pinia)已执行(通常在main.js中先注册 Pinia)。
代码示例
// src/utils/request.js(Axios 封装)import axios from'axios'import{ createPinia }from'pinia'import{ useUserStore }from'@/stores/user'// 初始化 Pinia(组件外使用 Store 必须)const pinia =createPinia()const userStore =useUserStore(pinia)const service = axios.create({baseURL:'/api',timeout:10000})// 请求拦截器:自动带 Token service.interceptors.request.use((config)=>{// 从 Pinia Store 中读取 Tokenconst token = userStore.token if(token){// 添加到请求头(具体字段看后端文档) config.headers['Authorization']=`Bearer ${token}`}return config },(error)=> Promise.reject(error))exportdefault service 注意事项
- ❌ 不要在组件外直接
useUserStore():必须先传入pinia实例,否则会报错; - ❌ 不要在每个请求里手动加 Token:用请求拦截器统一处理;
- ❌ 不要写错请求头字段名:必须和后端约定一致。
三、第三步:处理 Token 过期
通用场景
Token 过期后,后端返回 401 状态码,需清除 Token 并跳转到登录页。
Pinia 通用方案
- 在 Axios/Fetch 封装的响应拦截器中判断
401状态码; - 调用 Pinia Store 的
logoutaction 清除 Token; - 强制跳转到登录页。
代码示例
// 1. Pinia Store 补充 logout action(src/stores/user.js)exportconst useUserStore =defineStore('user',{state:()=>({token: localStorage.getItem('token')||''}),actions:{asynclogin(loginForm){/* 登录逻辑 */},// 登出/清除 Token actionlogout(){// 直接修改 statethis.token =''// 清除本地存储 localStorage.removeItem('token')}}})// 2. 响应拦截器补充(src/utils/request.js)import router from'@/router'// 需结合路由 service.interceptors.response.use((response)=> response.data,(error)=>{if(error.response && error.response.status ===401){alert('登录已过期,请重新登录')// 调用 Pinia Store 的 logout action userStore.logout()// 跳转到登录页 router.replace('/login')}else{alert('请求失败:'+(error.response?.data?.msg ||'网络错误'))}return Promise.reject(error)})注意事项
- ❌ 不要只清除 Pinia 不清除本地存储:下次刷新页面旧 Token 又会被读出来;
- ❌ 不要忘记跳登录页:Token 过期后用户无法继续操作;
- ❌ 跳转登录页建议用
replace:避免用户点击“回退”回到过期页面。
四、第四步:用户主动登出
通用场景
用户点击“退出登录”按钮,需清除所有登录相关数据。
Pinia 通用方案
- 给用户二次确认;
- 调用 Pinia Store 的
logoutaction; - 跳转到登录页。
代码示例
<script setup> import { useUserStore } from '@/stores/user' import { useRouter } from 'vue-router' const userStore = useUserStore() const router = useRouter() const handleLogout = () => { if (confirm('确定要退出登录吗?')) { // 调用 Pinia Store 的 logout action userStore.logout() alert('退出成功') router.replace('/login') } } </script> 注意事项
- ❌ 不要省略二次确认:避免用户误触;
- ❌ 登出后要彻底清除:包括 Pinia state、本地存储、用户信息(如果有);
- ❌ 若后端有登出接口,建议先调用接口再清除前端数据。
五、第五步:路由权限控制(进阶但常用)
通用场景
未登录用户不能访问受保护页面(如首页、个人中心),已登录用户不能访问登录页。
Pinia 通用方案
- 在 Vue Router 的
beforeEach全局前置守卫中读取 Pinia Store; - 定义白名单,判断用户是否登录;
- 根据判断结果决定放行或跳转。
代码示例
// src/router/index.jsimport{ createRouter, createWebHistory }from'vue-router'import{ createPinia }from'pinia'import{ useUserStore }from'@/stores/user'const routes =[{path:'/login',component:()=>import('@/views/Login.vue')},{path:'/home',component:()=>import('@/views/Home.vue'),meta:{requiresAuth:true}}]const router =createRouter({history:createWebHistory(), routes })// 初始化 Pinia(路由守卫中使用 Store 必须)const pinia =createPinia()// 全局前置路由守卫 router.beforeEach((to, from, next)=>{const userStore =useUserStore(pinia)const token = userStore.token const whiteList =['/login','/404']if(token){ to.path ==='/login'?next('/home'):next()}else{ whiteList.includes(to.path)?next():next('/login')}})exportdefault router 注意事项
- ❌ 不要在路由守卫中直接
useUserStore():必须先传入pinia实例; - ❌ 不要忘记定义白名单:避免无限重定向;
- ❌ 前端权限控制仅做拦截:真正的权限校验必须在后端做。
总结:Pinia Token 全流程核心逻辑
- 获取+存储:登录接口返回 → Pinia action 直接修改 state + 存本地存储;
- 使用:请求拦截器从 Pinia Store 读 Token → 自动加请求头;
- 过期:响应拦截器处理 401 → 调用 Pinia logout action → 跳登录页;
- 登出:用户确认 → 调用 Pinia logout action → 跳登录页;
- 控制:路由守卫从 Pinia Store 读 Token → 白名单判断。
Pinia 专属避坑指南
- ❌ 不要用 Vuex 的
commit/mutations思维:Pinia 直接在 actions 中修改this.xxx; - ❌ 不要在 Vue 组件外直接
useStore():必须先传入pinia实例; - ❌ state 不要写成对象:必须是函数返回对象(
state: () => ({ token: '' })); - ❌ 不要忘记同时存 Pinia 和本地存储:Pinia 刷新会清空。