Vue3 Pinia Token 全流程操作指南
本文梳理 Vue3 官方推荐状态管理库 Pinia 下的 Token 操作逻辑,剥离具体项目细节,聚焦「获取→存储→使用→过期→清除」的核心生命周期,结合 Pinia 「无 mutations、actions 直接异步、模块化更简单」的特性,每个步骤均标注「通用场景+Pinia 通用方案 + 注意事项」,可直接作为开发速查表。
梳理 Vue3 状态管理库 Pinia 下的 Token 操作逻辑,涵盖获取存储、请求携带、过期处理、主动登出及路由权限控制五大核心步骤。通过对比 Pinia 与 Vuex 区别,提供 Axios 封装及 Store 定义示例,强调无 mutations、actions 直接异步等特性,帮助开发者构建安全可靠的认证流程。
本文梳理 Vue3 官方推荐状态管理库 Pinia 下的 Token 操作逻辑,剥离具体项目细节,聚焦「获取→存储→使用→过期→清除」的核心生命周期,结合 Pinia 「无 mutations、actions 直接异步、模块化更简单」的特性,每个步骤均标注「通用场景+Pinia 通用方案 + 注意事项」,可直接作为开发速查表。
| 特性 | Vuex | Pinia |
|---|
| 修改状态的方式 | 必须通过 commit 调用 mutations | actions 中可直接修改 state |
| 异步处理 | actions 处理异步,mutations 同步 | actions 可直接写 async/await |
| 模块化 | 需开启 namespaced: true 隔离 | 天然模块化,无需额外配置 |
| TypeScript 支持 | 配置复杂 | 原生支持,类型推导友好 |
用户通过账号密码/验证码等方式登录,后端验证通过后返回 Token,需将 Token 存入 Pinia(全局共享)和本地存储(刷新不丢)。
user 模块的 Pinia Store,包含 token 状态和 login 异步 action;login action 中调用登录接口,提取 Token;// 1. 定义 Pinia Store(src/stores/user.js)
import { defineStore } from 'pinia'
import { loginAPI } from '@/api/user'
export const useUserStore = defineStore('user', {
// state:必须是函数返回对象(避免组件间共享状态)
state: () => ({
token: localStorage.getItem('token') || '' // 初始化时从本地存储读取
}),
// actions:直接处理异步,可直接修改 state
actions: {
async login(loginForm) {
// 调用登录接口
const res = await loginAPI(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()
const handleLogin = async () => {
try {
await userStore.login({ username: 'admin', password: '123456' })
alert('登录成功')
// 跳转到首页(需结合路由)
} catch (err) {
alert('登录失败:' + (err.response?.data?.msg || '未知错误'))
}
}
</script>
commit,直接 this.token = token 即可;每次发起业务请求时,自动从 Pinia Store 中读取 Token 并添加到请求头。
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 中读取 Token
const token = userStore.token
if (token) {
// 添加到请求头(具体字段看后端文档)
config.headers['Authorization'] = `Bearer ${token}`
}
return config
}, (error) => Promise.reject(error))
export default service
useUserStore():必须先传入 pinia 实例,否则会报错;Token 过期后,后端返回 401 状态码,需清除 Token 并跳转到登录页。
401 状态码;logout action 清除 Token;// 1. Pinia Store 补充 logout action(src/stores/user.js)
export const useUserStore = defineStore('user', {
state: () => ({
token: localStorage.getItem('token') || ''
}),
actions: {
async login(loginForm) {
/* 登录逻辑 */
},
// 登出/清除 Token action
logout() {
// 直接修改 state
this.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)
}
)
replace:避免用户点击'回退'回到过期页面。用户点击'退出登录'按钮,需清除所有登录相关数据。
logout action;<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>
未登录用户不能访问受保护页面(如首页、个人中心),已登录用户不能访问登录页。
beforeEach 全局前置守卫中读取 Pinia Store;// src/router/index.js
import { 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')
}
})
export default router
useUserStore():必须先传入 pinia 实例;commit/mutations 思维:Pinia 直接在 actions 中修改 this.xxx;useStore():必须先传入 pinia 实例;state: () => ({ token: '' }));
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online