Day 4 登录页及路由 (二) -- Vue状态管理

news2024/12/25 13:44:03

状态管理

之前的实现中,判断登录状态用了伪实现,实际当中,应该是以缓存中的数据为依据来进行的。这就涉及到了应用程序中的状态管理。在Vue中,状态管理之前是Vuex,现在则是推荐使用Pinia,在脚手架项目创建过程中,也提示了是否使用。

通过Pinia官方文档,可以看到核心概念Store的定义:

Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。

而关于何时使用也有一个指南:

一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。另一方面,你应该避免在 Store 中引入那些原本可以在组件中保存的本地数据,例如,一个元素在页面中的可见性。

 对于登录状态,显然就是一个需要在各个地方都可以访问的数据。

一般而言,用户登录后,会把一些基础信息,比如用户名,token,角色,权限等缓存起来,用于全局使用。

实现

而按照官方文档推荐,定义一个interface用于类型推断:

src\stores\interfaces\index.ts 

import type { Ref } from 'vue'

/**
 * 用户信息
 */
export interface UserInfo {
  /**
   * 名字
   */
  name: string
}

/**
 * 登录用户状态
 */
export interface UserState {
  /**
   * 令牌
   */
  token: Ref<string>
  /**
   * 用户信息
   */
  userInfo: Ref<UserInfo>
  /**
   * 设置令牌
   * @param token 令牌   
   */
  setToken: (token: string) => void
  /**
   * 设置用户信息
   * @param userInfo 用户信息
   */
  setUserInfo: (userInfo: UserInfo) => void
}

然后,定义CurrentUserStore:

src\stores\currentUser.ts

import { ref } from 'vue'
import { defineStore } from 'pinia'
import type { UserInfo, UserState } from '@/stores/interfaces'

export const useCurrentUserStore = defineStore(
  'currentUser',
  (): UserState => {
    const token: Ref<string> = ref('')
    const userInfo: Ref<UserInfo> = ref({ name: '' })

    function setToken(value: string) {
      token.value = value
    }

    function setUserInfo(value: UserInfo) {
      userInfo.value = value
    }

    return { token, userInfo, setToken, setUserInfo }
  },
  {
    persist: true
  }
)

这里面在官方文档中defineStore的第二个参数其实有一个选项式和组合式的选择,个人觉得组合式相对精简,当然,这也是因为框架做了不少工作。

关于store的定义,额外需要说明的就是第三个参数,里面多了一个 persist:true。这个实际上用到了Pinia的插件——pinia-plugin-persistedstate。很有意思的是,Pinia算是Vue的插件,而它本身也有一个插件体系。

要使用pinia-plugin-persistedstate,首先需要安装包

npm install pinia-plugin-persistedstate

然后修改main.ts,使用之:

src\main.ts

import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const app = createApp(App)

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

app.use(pinia)

至此,Store已经定义好了,应该可以使用了,不过在使用之前,还是先写一个单元测试来验证一下。之前的脚手架创建时,就已经选择了Vitest作为单元测试框架,因此,在src\component下已经创建了一个_test_文件夹,单元测试代码就写在这里。具体如下:

src\components\__tests__\currentUser.spec.ts

import { describe, beforeEach, it, expect } from 'vitest'
import { createApp } from 'vue'
import type {UserState, UserInfo} from '@/stores/interfaces'
import { setActivePinia, createPinia,storeToRefs  } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { useCurrentUserStore } from '@/stores/currentUser'

describe('Current User Store', () => {
  const app = createApp({})  
  const testToken = 'TestToken 1234'
  beforeEach(() => {
    // 创建一个新 pinia,并使其处于激活状态,这样它就会被任何 useStore() 调用自动接收
    // 而不需要手动传递:
    // 在 pinia 被安装在一个应用之后,插件才会被使用
    const pinia = createPinia().use(piniaPluginPersistedstate)
    app.use(pinia)
    setActivePinia(pinia)
  })

  it('setToken', () => {
    const currentUser = useCurrentUserStore()
    expect(currentUser.token).toBe('')
    currentUser.setToken(testToken)
    expect(currentUser.token).toBe(testToken)
  })

  it('setUserInfo', ()=>{
    const currentUser = useCurrentUserStore()        
    const {userInfo} = storeToRefs(currentUser)
    // const {userInfo} = currentUser
    expect(currentUser.userInfo.name).toBe('')    
    expect(userInfo.value.name).toBe('')
    // expect(userInfo.name).toBe('')
    const name = 'test user'
    currentUser.setUserInfo({name:name})
    expect(currentUser.userInfo.name).toBe(name)
    expect(userInfo.value.name).toBe(name)
    // expect(userInfo.name).toBe(name)  // 这里会失败,因为userInfo 解包成非响应式引用
  })
})

参照Pinia官方文档关于测试的描述,使用beforeEach来构造测试环境。另外,注意在 setUserInfo这个case中,特意演示了 storeToRefs的用法,这样解包才能保证token、userInfo依然是响应式的,注释掉的部分说明了直接解包的话,数据变更不会反映到直接解包的变量上。

OK,前戏已经差不多可以了,现在可以进入主题,修改router中的validateLoginState方法了。

因为是前端判断,token的其它校验就留给后端,前端仅判断token是否为空就可以了,代码就变得很简单:

src\router\index.ts

/**
 * 校验登录是否有效
 * @returns True: 登录状态有效 False:登录状态无效
 */
const validateLoginState = () => {
  const currentUser = useCurrentUserStore()
  return (currentUser.token != '') 
}

有了校验,就需要有地方可以设置token,修改一下LoginView,加一个FakeLogin:

src\views\login\LoginView.vue

<template>
    Logged On ? <el-text class="mx-1" type="info">{{ isLogon }}</el-text>
    <RouterLink to="/">Go to Home</RouterLink>
    <el-button type="primary" @click="fakeLogin">Fake Login</el-button>
</template>
<script setup lang="ts">
import { useCurrentUserStore } from '@/stores/currentUser'
const currentUser = useCurrentUserStore()
const isLogon = computed(() => currentUser.token.length > 0)
function fakeLogin(event: Event) {
    currentUser.setToken("faked login token")
}
</script>

样式比较丑,先体验效果:

这个时候,试着点击Go to Home按钮,页面不会动,因为还未登录,在DevTools中查看Pinia如下,token为空字符串:

点击Fake Login 按钮,模拟登录后,状态改变:

页面也有变化(这里红框中的部分也是体验了一下响应式,用了一个computed,这样登录状态会根据token自动变化,后续登陆后的页面框架也可以用这样的方式来处理token失效、退出事件)

再在HomeIndex.vue中,添加一个LogOff按钮,整个过程就完整了。logoff方法就做了两个动作,清除token,重定向到登录页。

src\views\home\HomeIndex.vue

<template>
    Home
    <el-button type="primary" @click="logoff">Log Off</el-button>
</template>
<script setup lang="ts">
import { useCurrentUserStore } from '@/stores/currentUser'

import { useRouter } from "vue-router"
import { LOGIN_URL } from "@/config"

const currentUser = useCurrentUserStore()
const router = useRouter()
/**
 * 推出登录
 */
function logoff(event: Event) {
    // 清除token
    currentUser.setToken('')
    // 重定向到登录页
    router.replace(LOGIN_URL);
}

</script>

效果如下,一如既往的丑:

至此,基于Pinia的登录状态管理已经完成了。

回顾

回过头来再看,Pinia本身的概念不复杂,state,getter,action,三个核心概念构成了store整体。从后端开发的角度来看,感觉会很眼熟,Repository + DTO,或者当成一个领域对象来看,有数据,有查询,有操作。那么是不是可以把这个模式扩大到整个应用程序呢,比如getter直接调用api获取后端数据,action也是直接调用api来实现数据操作。从道理上来讲应该是可以的,但是那就是把store当成了领域层,localStorage作为缓存了。而在前端是否需要这么复杂的架构,还是需要仔细考量的。我感觉如果不是特别复杂的应用上,没有必要使用这种方式来开发。毕竟前端是运行在浏览器中,性能还是比较重要的。即使有各种优化措施,这种框架带来的概念上的简化也并没有太多的优势,因为本身大部分情况下,前端页面还没有这么复杂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1152824.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【年终特惠】全流程HEC-RAS 1D/2D水动力与水环境模拟技术案例实践及拓展应用

水动力与水环境模型的数值模拟是实现水资源规划、环境影响分析、防洪规划以及未来气候变化下预测和分析的主要手段。然而&#xff0c;一方面水动力和水环境模型的使用非常复杂&#xff0c;理论繁复&#xff1b;另一方面&#xff0c;免费的水动力和水环境软件往往缺少重要功能&a…

什么是神经网络,它的原理是啥?(2)

参考&#xff1a;https://www.youtube.com/watch?vmlk0rddP3L4&listPLuhqtP7jdD8CftMk831qdE8BlIteSaNzD 视频3&#xff1a;什么是激活函数&#xff1f;为什么我们需要激活函数&#xff1f;它的类型有哪些&#xff1f; 为什么需要激活函数&#xff1f;如果没有激活函数&…

无品牌国产PLC模块调试说明

地址30001对应的aiw9 30002对应aiw10 30003 aiw11 30004 aiw12 模块接线及拨码全部向下&#xff0c;对应的DeviceID为15地址 使用串口线链接的时候a要接b0 b接a0 要反着接才能有数据

LeetCode|动态规划|1035. 不相交的线 、53. 最大子数组和

目录 一、1035. 不相交的线 1.题目描述 2.解题思路 3.代码实现 二、53. 最大子数组和 1.题目描述 2.解题思路 3.代码实现&#xff08;动态规划解法&#xff09; 一、1035. 不相交的线 1.题目描述 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现…

学习Linux/GNU/C++/C过程中遇到的问题

学习Linux/GNU/C/C过程中遇到的问题 1.源函数调用&#xff1a;2.linux静态库使用&#xff1a;3.vscode创建c程序调用onnxruntime:问题1&#xff1a;找不到头文件或者未定义函数问题2:error while loading shared libraries: libonnxruntime.so.1.16.1: cannot open shared obje…

HQChart使用教程97-K线X轴滚动条

HQChart使用教程97-K线X轴滚动条 效果图创建步骤1. 创建滚动条div2. 初始化滚动条实例3. 配置滚动条属性4. 创建滚动条5. K线图和滚动条绑定6. 滚动条显示位置 完整示例HQChart代码地址 效果图 示例地址&#xff1a;https://jones2000.github.io/HQChart/webhqchart.demo/sampl…

Python武器库开发-高级特性篇(十)

高级特性篇(十) 多进程 多进程就是指通过应用程序利用计算机的多核资源达到同时执行多个任务的目的&#xff0c;以此来提升程序的执行效率 import os from multiprocessing import Processdef hello(name):#获取当前进程#os.getpid()print(process {}.format(os.getpid()))p…

内衣洗衣机和手洗哪个干净?好用的内衣洗衣机测评

最近一段时间&#xff0c;关于内衣到底是机洗好&#xff0c;还是手洗好这个话题&#xff0c;有很多人都在讨论&#xff0c;坚决的手洗党觉得应该用手来清洗&#xff0c;机洗与其它衣物混合使用&#xff0c;会产生交叉感染&#xff0c;而且随着使用时间的推移&#xff0c;会变得…

【ChatGLM2-6B】从0到1部署GPU版本

准备机器资源 显卡: 包含NVIDIA显卡的机器&#xff0c;如果是阿里云服务器可以选择ecs.gn6i-c4g1.xlarge规格硬盘: 大约50G左右操作系统: CentOS 7.9 64位CPU内存: 4C16G 更新操作系统 sudo yum update -y sudo yum upgrade -y下载并安装anaconda 在命令行中&#xff0c;输…

python--杂识--15--python调用c代码

两种方法&#xff1a; Python/C apictypes 1 Python/C api 1.1编写代码 c_test.c #include <Python.h>// C的原生函数&#xff0c;实现两个整数的相加 int add(int a, int b) {return a b; };// compute_add【一般&#xff1a;模块名_函数名】&#xff0c;按照pyth…

渗透测试-Fastjson反序列化漏洞getshell

目录 前言 测试环境准备 dnslog测试 搭建rmi服务器&准备恶意类 引用JdbcRowSetImpl攻击 反弹shell$命令执行 总结 关键字&#xff1a;fastjson 1.2.24反序列化导致任意命令执行漏洞 注&#xff1a;本次渗透测试全在虚拟机中进行仅用于学习交流&#xff0c;请勿在实…

直播间讨论区需要WebSocket,简单了解下

由于 http 存在一个明显的弊端&#xff08;消息只能有客户端推送到服务器端&#xff0c;而服务器端不能主动推送到客户端&#xff09;&#xff0c;导致如果服务器如果有连续的变化&#xff0c;这时只能使用轮询&#xff0c;而轮询效率过低&#xff0c;并不适合。于是 WebSocket…

QT-- out of memory, returning null image

提示&#xff1a;本文为学习内容&#xff0c;若有错误&#xff0c;请及时联系作者&#xff0c;谦虚受教 文章目录 前言一、崩溃信息二、错误原因1.QImage2.QStandardItemModel 三、问题解决总结 前言 学如逆水行舟&#xff0c;不进则退。 一、崩溃信息 崩溃信息: QImage: out…

UIButton

titleEdgeInsets和imageEdgeInsets titleEdgeInsets和imageEdgeInsets的作用是用来移动btn两个子空间的排布的 它们只是image和label相较于原来位置的偏移量&#xff0c;那什么是原来的位置呢&#xff1f;其实就是不设置Insets的那个状态。下面为不设置insets的状态。 默认情…

AGMZE-A-32/100、AGMZE-A-10/350比例溢流阀控制器

AGMZO-A-10/315、AGMZO-A-20/210、AGMZO-A-32/100、AGMZO-A-10/50、AGMZO-A-20/350、AGMZE-A-10/50、AGMZE-A-20/210、AGMZE-A-32/100、AGMZE-A-10/350、AGMZE-A-20/50锥阀型&#xff0c;先导式&#xff0c;数字型比例溢流阀&#xff0c;用于压力开环控制。 A型&#xff0c;与…

风格迁移常用代码

nn.MSELoss均方损失函数 LPIPS感知损失 学习感知图像块相似度(Learned Perceptual Image Patch Similarity, LPIPS)也称为“感知损失”(perceptual loss)&#xff0c;用于度量两张图像之间的差别&#xff0c;来源于论文《The Unreasonable Effectiveness of Deep Features as …

C# Dictionary类,确实有点东西

前言&#xff1a; 今天这篇文章是对Dictionary类的学习&#xff0c;Dictionary类是一个字典序&#xff0c;我们在编程中经常用到&#xff0c;它算是enum枚举类型和list类型的结合&#xff0c;是以键值对的形式去存储值的&#xff0c;那么你会这个知识点不&#xff0c;不会那么…

csapp datalab

知识点总结 1. 逻辑运算符关系 and&#xff08;与&#xff09;、or&#xff08;或&#xff09;和xor&#xff08;异或&#xff09;是逻辑运算符&#xff0c;用于对布尔值进行操作。它们可以在不同的逻辑表达式之间进行转换。下面是and、or和xor之间的转换规则&#xff1a; a…

答题小程序源码个人每日答题怎么做

答题小程序源码之个人每日答题怎么做 该模式以个人学习答题的方式进行答题&#xff0c;每人每天有X次答题机会&#xff0c;答对一题得X分&#xff0c;连续答对有额外奖励积分&#xff0c;每道题有倒计时X秒的思考时间。答题完成后领取本次的奖励积分。答题过程中如发现题目或答…