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

状态管理

之前的实现中,判断登录状态用了伪实现,实际当中,应该是以缓存中的数据为依据来进行的。这就涉及到了应用程序中的状态管理。在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作为缓存了。而在前端是否需要这么复杂的架构,还是需要仔细考量的。我感觉如果不是特别复杂的应用上,没有必要使用这种方式来开发。毕竟前端是运行在浏览器中,性能还是比较重要的。即使有各种优化措施,这种框架带来的概念上的简化也并没有太多的优势,因为本身大部分情况下,前端页面还没有这么复杂。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux查看系统版本、内核信息、操作系统类型版本

1. 使用 uname 命令&#xff1a;这将显示完整的内核版本信息&#xff0c;包括内核版本号、主机名、操作系统类型等。 uname -a2. 使用 lsb_release 命令&#xff08;仅适用于支持 LSB&#xff08;Linux Standard Base&#xff09;的发行版&#xff09;&#xff1a;这将显示包含…

HCIE怎么系统性学习?这份HCIE学习路线帮你解决

华为认证体系覆盖ICT行业十一个技术领域共十三个技术方向的认证&#xff0c;今天我们分享的是其中最热门的数据通信方向的HCIE学习路线。 HCIE是华为认证体系中最高级别的ICT技术认证 &#xff0c;旨在打造高含金量的专家级认证&#xff0c;为技术融合背景下的ICT产业提供新的能…

JVS-BI数字大屏设计器:一站式解决方案

数字大屏介绍 数字大屏是当下数据展示、业务监控、指挥调度常见的业务表达形态&#xff0c;常有可视化的图表、效果装饰、事件操作等技术组成酷炫的效果展示。 配置入口 进入JVS-BI&#xff08;bi.bctools.cn&#xff09;&#xff0c;进入大屏页面&#xff0c;如下图所示 ①…

TypeScript之函数以及与JavaScript函数的区别

一、是什么 函数是JavaScript 应用程序的基础&#xff0c;帮助我们实现抽象层、模拟类、信息隐藏和模块 在TypeScript 里&#xff0c;虽然已经支持类、命名空间和模块&#xff0c;但函数仍然是主要定义行为的方式&#xff0c;TypeScript 为 JavaScript 函数添加了额外的功能&…

Docker 部署spring-boot项目(超详细 包括Docker详解、Docker常用指令整理等)

文章目录 DockerDocker的定义Docker有哪些作用Docker有哪些好处使用docker部署springboot项目安装docker创建Dockerfile镜像文件执行镜像文件(Dockerfile文件)查看Docker镜像启动容器查看Docker中运行的容器查看服务容器日志 Docker常用指令查看docker安装目录启动Docker停止Do…

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

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

金属压铸件自动化3D全尺寸测量设备自动外观检测三维检测-CASAIM

铸造作为现代装备制造工业的基础共性技术之一&#xff0c;铸件产品既是工业制造产品&#xff0c;也是大型机械的重要组成部分&#xff0c;被广泛运用在航空航天、工业船舶、机械电子和交通运输等行业。 铸件形状复杂&#xff0c;一般的三坐标或者卡尺圆规等工具难以获取多特征…

论文阅读——BART

Arxiv: https://arxiv.org/abs/1910.13461 一个去噪自编码器的预训练序列到序列的模型。是一个结合了双向和自回归transformers的模型。 预训练分为两个阶段&#xff1a;任意噪声函数破坏文本和序列模型重建原始文本 一、模型 input&#xff1a;被破坏的文本-->bidirecti…

【开发日记】必须记录一下困扰我两天的问题 MyBatisPlus适配达梦insert时提示:无效的列

【需求】 项目ORM框架使用的是MyBatisPlus&#xff0c;数据库原来使用的是MySQL&#xff0c;现在需要适配达梦。 【问题】 项目ORM框架使用的是MyBatisPlus&#xff0c;数据库原来使用的是MySQL&#xff0c;现在需要适配达梦数据库。 在适配过程中查询、更新、删除都没有问题…

购物车死了吗?拼多多的社交电商革命

亲爱的小伙伴们&#xff0c;大家好&#xff01;我是小米&#xff0c;今天要和大家聊一聊一个备受关注的话题&#xff1a;拼多多为什么没有购物车&#xff1f;这是一个网易产品经理面试题&#xff0c;但也是一个备受争议的话题。让我们一起来探讨一下吧&#xff01; 拼多多的购…

【idea】生成banner.txt

Spring Boot banner在线生成工具&#xff0c;制作下载英文banner.txt&#xff0c;修改替换banner.txt文字实现自定义&#xff0c;个性化启动banner-bootschool.netSpring Boot banner工具实现在线生成banner&#xff0c;轻松修改替换实现自定义banner&#xff0c;让banner.txt文…

Qt QWidget、QDialog、QMainWindow的区别

QWidget QWidget是Qt框架中最基础的窗口类&#xff0c;可以理解为用户界面的最基本单元。QWidget类提供了一个空白窗口&#xff0c;可以通过继承该类来创建自定义的窗口类。QWidget类提供了基本的窗口属性和方法&#xff0c;如大小、位置、标题、图标等。 QDialog QDialog是…

基于计算机视觉的 Transformer 研究进展

论文地址&#xff1a; https://kns.cnki.net/kcms/detail/11.2127.tp.20211129.1135.004.html 18页&#xff0c;74篇参考文献 目录 摘 要 1 Transformer 基本原理 1.1 编码器-解码器 1.2 自注意力 1.3 多头注意力 2 在计算机视觉领域的应用 2.1 图像分类 2.1.1 iGPT …

算法通过村第十七关-贪心|白银笔记|贪心高频问题

文章目录 前言区间问题判断区间是否重复合并区间插入区间 字符串分割加油站问题总结 前言 提示&#xff1a;如果生活把你的门关上了&#xff0c;那你就再打开&#xff0c;这就是门&#xff0c;门就是这样的。 --佚名 贪婪的思想不一定要理解的很透彻&#xff0c;但是贪婪的问题…

优优嗨聚集团:绝味鸭脖市值上升,餐饮业迎来新变革

导语&#xff1a;绝味鸭脖作为中国餐饮行业的领军企业&#xff0c;其市值上升不仅体现了企业的市场价值&#xff0c;更对整个餐饮行业产生了深远的影响。本文将探讨绝味鸭脖市值上升对餐饮行业的影响&#xff0c;以及未来餐饮行业的发展趋势。 一、绝味鸭脖市值上升&#xff0c…

50元买来的iPhone手机刷机经验

前段时间&#xff0c;家里的iPad被家人误操作&#xff0c;导致iPad变成不可使用状态。自己折腾了半天&#xff0c;没有找到解决办法。没有办法&#xff0c;只好拿到手机维修店去修理,很快就修理好了.其实也很简单--就是对iPad进行了刷机操作。当然我也看到了刷机的方法。今天&a…

pytorch复现3_GoogLenet

背景&#xff1a; GoogLeNeta是2014年提出的一种全新的深度学习结构&#xff0c;在这之前的AlexNet、VGG等结构都是通过增大网络的深度(层数)来获得更好的训练效果&#xff0c;但层数的增加会带来很多负作用&#xff0c;比如overfit、梯度消失、梯度爆炸等。GoogLeNet通过引入i…

网络编程服务端与客户端存在的端口问题

服务端的窗口不能再次使用的原因如下&#xff1a; 服务器端的窗口不能再次使用的原因可能有以下几点&#xff1a; 1. 窗口已经关闭&#xff1a;如果服务器端的窗口已经被关闭&#xff0c;那么就无法再次使用。关闭窗口后&#xff0c;服务器会释放相关资源&#xff0c;包括与该…

【机器学习】项目数据处理部分

文章目录 前言项目理解数据探索特征工程总结 前言 本文参考《阿里云天池大赛赛题解析》&#xff0c;拿到一个项目或者赛题&#xff0c;使用机器学习来进行预测分类&#xff0c;需要以下七个步骤&#xff1a; 项目&#xff08;赛题&#xff09;理解数据探索特征工程模型训练模…