十大创意网站,线上引流线下推广方案,医院网站建设套餐方案,广州网站公司推荐前文vuex 过于详细#xff0c;现在还是应该用pinia
看一眼下图基本明白它的流程了吧 使用步骤#xff1a;
Pinia 使用步骤#xff1a;以登录功能为例
Pinia 是 Vue.js 的新一代状态管理库#xff0c;使用起来非常简单。下面我以 用户登录 功能为例#xff0c;演示 Pini… 前文vuex 过于详细现在还是应该用pinia
看一眼下图基本明白它的流程了吧 使用步骤
Pinia 使用步骤以登录功能为例
Pinia 是 Vue.js 的新一代状态管理库使用起来非常简单。下面我以 用户登录 功能为例演示 Pinia 的基本使用步骤
一、安装 Pinia
npm install pinia二、创建并配置 Pinia 实例 // main.js
import { createApp } from vue
import { createPinia } from pinia
import App from ./App.vueconst pinia createPinia()
const app createApp(App)app.use(pinia) // 注册 Pinia
app.mount(#app)我们项目是这样创建的
import { createApp } from vue
import { createPinia } from pinia
import router from ./router
import ElementPlus from element-plus;
import element-plus/theme-chalk/index.css;
import App from ./App.vue
import Axios from axiosconst appcreateApp(App)
app.use(router)
app.use(createPinia())
app.use(ElementPlus)
app.mount(#app)三、定义用户 Store核心
例子1
// store/user.js
import { defineStore } from pinia
import { ref, computed } from vueexport const useUserStore defineStore(user, () {// 1. 状态存储用户数据const user ref(null) // 用户信息const isLoggedIn ref(false) // 登录状态// 2. getters计算属性获取派生状态const username computed(() user.value?.username || )// 3. actions修改状态的方法const login async (credentials) {try {// 模拟 API 请求const response await fetch(/api/login, {method: POST,body: JSON.stringify(credentials)})const data await response.json()// 更新状态user.value data.userisLoggedIn.value truereturn true // 登录成功} catch (error) {console.error(登录失败:, error)return false}}const logout () {user.value nullisLoggedIn.value false}return {user,isLoggedIn,username,login,logout}
})Store 结构 state用 ref 定义响应式数据如 user、isLoggedIngetters用 computed 定义计算属性如 usernameactions定义修改状态的方法如 login、logout 响应式原理 当 user 或 isLoggedIn 变化时所有依赖它们的组件会自动更新。 Pinia 登录与登出逻辑
这段代码是 Pinia store 中处理用户认证的核心逻辑
一、登录逻辑login 方法
1. 整体功能
这是一个 异步方法负责处理用户登录流程 发送登录请求到后端 API验证用户凭证保存用户信息和登录状态返回登录结果成功 / 失败
2. 参数与结构 const login async (credentials) { ... }credentials用户输入的凭证通常是 { username, password } 对象 const login async (credentials) {try {// 1. 发送 POST 请求到登录 APIconst response await fetch(/api/login, {method: POST,headers: {Content-Type: application/json},body: JSON.stringify(credentials) // 将凭证转为 JSON 格式})// 2. 解析响应数据const data await response.json()// 3. 更新状态user.value data.user // 保存用户信息如 id, username, tokenisLoggedIn.value true // 标记为已登录return true // 返回成功标志} catch (error) {console.error(登录失败:, error)return false // 返回失败标志}
}4. 关键细节 API 请求 使用 fetch 发送 POST 请求需设置 Content-Type: application/json 头用 JSON.stringify 将对象转为 JSON 字符串 错误处理 使用 try/catch 捕获网络错误或解析错误可能的错误包括网络超时、服务器错误、JSON 解析失败等 状态更新 user.value 存储完整用户信息如 { id: 1, username: test, token: xxx }isLoggedIn.value 作为登录状态标志方便其他组件快速判断
二、登出逻辑logout 方法
1. 整体功能
清除用户信息和登录状态实现用户退出登录。
2. 核心步骤 const logout () {user.value null // 清空用户信息isLoggedIn.value false // 标记为未登录
}3. 扩展应用
在实际项目中可能还需要 清除本地存储中的 token调用后端的登出 API如使 token 失效跳转到登录页或首页 可以这样扩展 const logout async () {try {// 调用后端登出 APIawait fetch(/api/logout, { method: POST })} catch (error) {console.warn(后端登出失败但继续清除前端状态, error)} finally {// 清除状态user.value nullisLoggedIn.value false// 清除本地存储localStorage.removeItem(auth_token)// 跳转到登录页需引入路由router.push(/login)}
}三、这个设计的优势 职责分离 登录逻辑集中在 store 中组件只需调用 login() 方法无需关心具体实现 响应式更新 当 user 或 isLoggedIn 变化时所有依赖它们的组件会自动更新 可测试性 可以轻松模拟 API 响应测试登录成功 / 失败的场景 扩展性 可以方便地添加额外功能如 token 刷新、登录状态持久化
四、常见问题及解决方案
问题 1登录成功后页面未更新
原因可能忘记在组件中使用响应式数据 解决方案确保在组件中正确引入 store const userStore useUserStore()
const { isLoggedIn } storeToRefs(userStore) // 使用 storeToRefs 保持响应式问题 2跨页面刷新后登录状态丢失
解决方案添加持久化插件如 pinia-plugin-persistedstate export const useUserStore defineStore(user, {// ...原有代码
}, {persist: true // 自动持久化到 localStorage
})问题 3如何处理 token 过期
解决方案在请求拦截器中检测 token 状态过期时自动刷新 // 假设使用 axios
axios.interceptors.response.use(response response,async error {if (error.response.status 401) { // token 过期const userStore useUserStore()await userStore.refreshToken() // 刷新 tokenreturn axios.request(error.config) // 重试原请求}return Promise.reject(error)}
)业务流程
登录验证凭证 → 保存用户信息 → 标记登录状态登出清除用户信息 → 标记未登录状态 我们遵循了 Pinia 的设计理念将状态管理逻辑与组件分离使代码更易维护和测试。 这里const user ref(null) 在初始状态下没有用户数据但这正是 Pinia/Vue 中管理异步数据的常见方式。
一、为什么初始值是 null 表示 “未加载” 状态 在用户登录前或数据未从后端获取时user 为空是合理的。此时组件可以显示 “加载中” 或 “请登录” 的提示。 避免空值错误 使用 ref(null) 而非直接 null 是为了保持响应式。当数据加载完成后修改 user.value 会触发所有依赖它的组件更新。 类型安全 在 TypeScript 中ref(null) 可以明确类型如 refUser | null(null)避免运行时类型错误。
二、数据何时被填充
当用户登录成功后login action 会更新 user 的值
const login async (credentials) {try {const response await fetch(/api/login)const data await response.json()// 登录成功后填充用户数据user.value data.user // -- 这里更新了 userisLoggedIn.value truereturn true} catch (error) {return false}
}三、如何在组件中安全使用
虽然初始值是 null但可以通过 可选链操作符?. 或 计算属性 安全访问
1. 直接使用可选链 templatediv v-ifuserStore.user欢迎{{ userStore.user.username }}/divdiv v-else请先登录/div
/template2. 使用计算属性提供默认值 const username computed(() user.value?.username || 未登录)// 在组件中使用
p{{ username }}/p // 永远不会报错要么显示用户名要么显示“未登录”第二个例子我们例子项目的定义如下
import { defineStore } from pinia
import { ref ,computed } from vue
import { postReq } from ../utils/api//用户仓库 提供数据、计算属性、方法
export const useUserStoredefineStore(user,(){//ref state //存放用户登录后的信息const userInforef({id:,token:,isAuth:false,})const isLoginref(false)//computed() getters//返回用户登录信息const getUserInfo computed((){console.log(getUserInfo:start)//pinia存储的用户信息和本地localStorageconsole.log(userInfo.value.isAuth);console.log(JSON.parse(localStorage.getItem(isAuth)));if(userInfo.value.isAuthfalse JSON.parse(localStorage.getItem(isAuth)) ){console.log(getUserInfo:更新用户信息)setAuthenticated(JSON.parse(localStorage.getItem(user)))}console.log(getUserInfo:end)return userInfo;}) //function() actions//设置登录状态获取登录信息const setAuthenticated(u){console.log(setAuthenticated:start)userInfo.valueu;//同步localStorageconsole.log(u.useType)localStorage.setItem(user,JSON.stringify(u));localStorage.setItem(token,u.token);localStorage.setItem(isAuth,u.isAuth);console.log(setAuthenticated:${userInfo.value.isAuth})console.log(setAuthenticated:end)}// const setLoginState(f){// isLogin.valuef// }//刷新tokenconst refreshToken(){// refreshTokenApi().then(response{// userStore.setAuthenticated({// token:response.data.access_token,// isAuth:true// })// })}//退出登录const logOut(){console.log(logOut:start)userInfo.value.isAuthfalseuserInfo.value.token//LocalStorage:除非主动删除否则会永久存储在浏览器中。localStorage.setItem(user,);localStorage.setItem(token,);localStorage.setItem(isAuth,false);//SessionStorage:只在当前所在窗口关闭前有效窗口关闭后其存储数据也就会被自动清除。sessionStorage.clear() console.log(logOut:end)}return {userInfo,getUserInfo,setAuthenticated,logOut}
}) 这是一个基于 Pinia 的用户认证模块包含了用户状态管理和认证流程。下面我将详细说明其中的 actions即修改状态的方法
一、核心 Action 功能解析
1. setAuthenticated(u)
作用设置用户认证状态并同步到本地存储 参数u包含用户信息的对象如 { id, token, isAuth } const setAuthenticated (u) {console.log(setAuthenticated:start)userInfo.value u; // 更新 Pinia 中的用户信息// 同步到 localStorage持久化存储localStorage.setItem(user, JSON.stringify(u));localStorage.setItem(token, u.token);localStorage.setItem(isAuth, u.isAuth);console.log(setAuthenticated:${userInfo.value.isAuth})console.log(setAuthenticated:end)
}关键逻辑 将用户信息保存到 Pinia 状态同时保存到 localStorage确保刷新页面后数据不丢失通常在登录成功后调用例如 // 登录成功后
const userData { id: 1, token: xxx, isAuth: true }
setAuthenticated(userData)2. logOut()
作用用户退出登录清除认证状态和本地存储 const logOut () {console.log(logOut:start)// 清除 Pinia 中的用户认证状态userInfo.value.isAuth falseuserInfo.value.token // 清除 localStorage 中的数据localStorage.setItem(user, );localStorage.setItem(token, );localStorage.setItem(isAuth, false);// 清除 sessionStorage通常用于临时数据sessionStorage.clear()console.log(logOut:end)
}关键逻辑 将 isAuth 标记为 false表示未登录清空 token 和用户信息清除 localStorage 和 sessionStorage 中的相关数据通常在用户点击 退出登录 按钮时调用
3. refreshToken()未完成
作用刷新用户 token用于保持会话避免频繁登录 const refreshToken () {// 未完成的实现// refreshTokenApi().then(response {// userStore.setAuthenticated({// token: response.data.access_token,// isAuth: true// })// })
}完整实现示例 const refreshToken async () {try {// 调用后端刷新 token 的 APIconst response await postReq(/api/refresh-token, {token: userInfo.value.token // 发送当前 token})// 更新用户信息主要是新的 tokensetAuthenticated({...userInfo.value,token: response.data.token,isAuth: true})return true} catch (error) {console.error(刷新 token 失败:, error)// 刷新失败时可以跳转到登录页logOut()return false}
}二、这些 Actions 的协作流程
1. 登录流程
组件调用登录 API如 postReq(/login)登录成功后获取用户信息和 token调用 setAuthenticated 保存用户信息和 token // 组件中的登录逻辑示例
async handleLogin() {const response await postReq(/login, this.formData)if (response.success) {setAuthenticated(response.data.user) // 保存用户信息router.push(/dashboard) // 跳转到主页}
}2. 登出流程
组件调用 logOut() action清除所有认证状态和存储数据跳转到登录页 // 组件中的登出按钮
button clicklogOut退出登录/button// 组件逻辑
import { useUserStore } from /store/usersetup() {const userStore useUserStore()const logOut () {userStore.logOut()router.push(/login)}return { logOut }
}3. 自动刷新 token
当 API 请求返回 token 过期 错误时自动调用 refreshToken // API 拦截器示例通常在 axios 中设置
axios.interceptors.response.use(response response,async error {if (error.response.status 401) { // token 过期const userStore useUserStore()const refreshed await userStore.refreshToken()if (refreshed) {// 刷新成功后重试原请求return axios(error.config)} else {// 刷新失败跳转到登录页router.push(/login)}}return Promise.reject(error)}
)三、代码优化
统一存储操作 可以创建工具函数封装 localStorage 操作避免重复代码
// utils/storage.js
export const setStorage (key, value) {localStorage.setItem(key, JSON.stringify(value))
}export const getStorage (key) {return JSON.parse(localStorage.getItem(key))
}export const removeStorage (key) {localStorage.removeItem(key)
}其他需要完善的地方 完善 refreshToken 实现 补充刷新 token 的具体逻辑处理可能的错误。 添加类型定义如果使用 TypeScript 为 userInfo 和相关方法添加类型提高代码安全性。 错误处理 在 setAuthenticated 和 logOut 中添加错误处理确保操作安全。
流程
登录通过 setAuthenticated 保存用户信息登出通过 logOut 清除所有认证数据token 管理通过 refreshToken 保持会话 它们的设计遵循了 Pinia 的最佳实践 将状态修改逻辑集中在 actions 中通过响应式机制自动更新依赖组件结合本地存储实现数据持久化 Pinia Getter 计算派生状态
例子代码中getters 部分定义了一个计算属性 username用于从用户状态中派生数据
一、Getter 的本质Vuex/Pinia 中的计算属性
在 Vuex/Pinia 中getters 类似于 Vue 组件中的 computed 属性主要用于 简化复杂状态访问避免在组件中重复编写复杂的状态获取逻辑缓存计算结果只有依赖的状态变化时才重新计算提高性能派生新状态从现有状态计算出新的值如过滤、转换格式等
二、代码中的 Getter 分析
1. username 计算属性 const username computed(() user.value?.username || )作用 安全地获取当前用户的用户名当用户未登录user.value 为 null时返回空字符串 当用户登录后自动返回 user.value.username 的值 关键点 使用 可选链操作符?. 避免 user.value 为 null 时的错误使用 || 提供默认值确保返回值类型始终为字符串依赖于 user 状态的变化当 user 更新时自动重新计算
三、Getter 的使用场景
1. 在组件中直接使用 templatediv v-ifisLoggedIn欢迎回来{{ username }}!/divdiv v-else请先登录/div
/templatescript
import { useUserStore } from /store/userexport default {setup() {const userStore useUserStore()return {isLoggedIn: userStore.isLoggedIn,username: userStore.username // 直接使用 getter}}
}
/script2. 在其他 Getter 中复用
// 在同一个 store 中定义另一个 getter
const welcomeMessage computed(() {if (isLoggedIn.value) {return 欢迎回来${username.value}!} else {return 请登录以继续}
})四、Getter 的优势 代码复用 多个组件可以共享同一个 getter避免重复编写相同的状态处理逻辑。 安全性 通过可选链和默认值避免在状态未初始化时出现错误。例如 // 错误直接访问可能为 null 的属性
console.log(user.value.username) // 当 user 为 null 时会报错// 安全通过 getter 访问
console.log(username.value) // 始终返回字符串 或实际用户名性能优化 计算结果会被缓存只有依赖的状态user变化时才会重新计算。
五、扩展更多 Getter 示例
1. 复杂计算
// 计算用户全名假设 user 有 firstName 和 lastName
const fullName computed(() {return user.value ? ${user.value.firstName} ${user.value.lastName} : 未登录用户
})2. 过滤列表
// 假设 store 中有一个 todos 数组
const completedTodos computed(() {return todos.value.filter(todo todo.completed)
})3. 带参数的 Getter // 通过 ID 获取用户返回一个函数
const getUserById computed(() {return (id) users.value.find(user user.id id)
})// 使用方式
const user getUserById.value(123)例子2中getter的使用 getUserInfo 是唯一的 getter它的设计很特别不仅返回用户信息还会在必要时从本地存储恢复状态。这是一个处理状态持久化的典型实现。
一、getUserInfo 的核心功能 const getUserInfo computed(() {console.log(getUserInfo:start)// 检查 Pinia 中的状态与 localStorage 是否不一致console.log(userInfo.value.isAuth);console.log(JSON.parse(localStorage.getItem(isAuth)));// 如果 Pinia 中未认证但 localStorage 显示已认证if (userInfo.value.isAuth false JSON.parse(localStorage.getItem(isAuth))) {console.log(getUserInfo:更新用户信息)setAuthenticated(JSON.parse(localStorage.getItem(user)))}console.log(getUserInfo:end)return userInfo; // 返回整个 userInfo 对象
})核心逻辑 状态同步检查 比较 userInfo.isAuthPinia 中的状态和 localStorage.isAuth本地存储的状态。 自动恢复状态 如果发现 Pinia 中的状态丢失如刷新页面后但本地存储中仍有用户信息则自动调用 setAuthenticated 恢复状态。 返回完整用户信息 无论是否需要恢复最终都返回最新的 userInfo 对象。
二、为什么需要这样设计
1. 解决页面刷新后状态丢失的问题
Vuex/Pinia 的状态默认存储在内存中页面刷新后会重置。而 localStorage 可以持久化保存数据。 这个 getter 的设计确保了 用户刷新页面后首次访问 getUserInfo 时会自动恢复之前的登录状态组件可以始终获取到最新的用户信息无需手动处理状态恢复
2. 保持状态一致性
通过在 getter 中添加同步逻辑确保 无论何时访问 getUserInfo得到的都是最新且一致的状态避免了在每个组件中重复编写状态恢复逻辑实现了逻辑复用
三、潜在问题与优化建议
1. 性能问题
每次访问 getUserInfo 都会读取 localStorage可能影响性能。 优化方案 可以添加一个标志位只在初始化时检查一次 const isInitialized ref(false)const getUserInfo computed(() {if (!isInitialized.value) {const localAuth JSON.parse(localStorage.getItem(isAuth))if (!userInfo.value.isAuth localAuth) {setAuthenticated(JSON.parse(localStorage.getItem(user)))}isInitialized.value true}return userInfo
})2. 类型安全问题
直接从 localStorage 解析的数据可能不符合预期格式。 优化方案 添加类型检查和默认值
const storedUser JSON.parse(localStorage.getItem(user) || {})
setAuthenticated({id: storedUser.id || ,token: storedUser.token || ,isAuth: storedUser.isAuth || false
})3. 代码冗余问题
当前设计中isLogin 状态与 userInfo.isAuth 重复。
这种在 getter 中添加副作用如状态恢复的做法并不常见但在处理持久化状态时是一种有效的模式。如果使用 pinia-plugin-persistedstate 插件这些逻辑可以被自动处理代码会更简洁。 四、在组件中使用 Store
1. 登录表单组件
!-- LoginForm.vue --
templatedivh2登录/h2input v-modelform.username placeholder用户名 /input v-modelform.password typepassword placeholder密码 /button clickhandleLogin登录/button/div
/templatescript
import { useUserStore } from /store/user
import { ref } from vueexport default {setup() {const userStore useUserStore()const form ref({username: ,password: })const handleLogin async () {const success await userStore.login(form.value)if (success) {console.log(登录成功)// 跳转到首页或其他操作} else {console.log(登录失败)}}return {form,handleLogin}}
}
/script2. 导航栏组件显示登录状态
!-- Navbar.vue --
templatenavdiv v-ifuserStore.isLoggedIn欢迎{{ userStore.username }}button clickuserStore.logout退出/button/divdiv v-elsebutton clickgoToLogin登录/button/div/nav
/templatescript
import { useUserStore } from /store/userexport default {setup() {const userStore useUserStore()const goToLogin () {// 跳转到登录页}return {userStore,goToLogin}}
}
/script使用方式 通过 useUserStore() 获取 store 实例直接调用 actions 修改状态通过 store 属性获取状态或计算属性
六、扩展添加持久化优化
如果需要在页面刷新后保留登录状态可以添加 pinia-plugin-persistedstate
npm install pinia-plugin-persistedstate修改 store/user.js
export const useUserStore defineStore(user, () {// ... 原有代码}, {// 启用持久化persist: {key: user-store,storage: localStorage,}
})总结
Pinia 的使用非常直观 定义 Store用 defineStore 创建数据仓库添加状态用 ref 定义数据添加计算属性用 computed 定义派生数据添加方法用普通函数定义修改状态的逻辑在组件中使用通过 useStore 获取实例并调用属性和方法 相比 VuexPinia 代码更简洁类型支持更好是 Vue 3 项目的首选状态管理方案。 下图从特定角度对比vuex 与pinia