基于Vue3做一套适合自己的状态管理(七)实践:当前登录用户的状态(严谨版)
计划章节
- 基类:实现辅助功能
- 继承:充血实体类
- 继承:OptionApi 风格的状态
- Model:正确的打开方式
- 组合:setup 风格,更灵活
- 注册状态的方法、以及局部状态和全局状态
- 实践:当前登录用户的状态
- 实践:列表页面需要的状态
做一个更严谨的登录用户状态
网站一般都需要知道现在访问的人是谁,以及相应的使用权限,所以需要用户登录,向后端提交用户名和密码,后端验证通过后,返回用户信息和权限(如果需要区分不同的权限的话)。
安全性 用户信息比较关键和敏感,只有在登录和退出的时候才可以变更,其他时候(组件)只能获取信息,不能变更信息。
那么在代码层面能不能做得严格一点呢?
一般流程
- 打开登录表单,录入用户名和密码,提交后端。
- 后端验证信息,如果通过,则返回用户信息。
- 前端得到用户信息后,可以记录用户信息,以便于各个组件使用。
- 前端提供一个安全退出的功能,用户点击后可以退出网站。
创建当前登录用户的状态
我们先设计一个这样的局部状态:
- ./store-nf/state-user
import { reactive, readonly } from 'vue'
import {
defineStore, // 创建状态
useStoreLocal // 获取局部状态
} from '@naturefw/nf-state'
export type User = {
name: string
isLogin: boolean
power: {
modules: Array<number | string>
}
}
export type UserState = {
user: User
}
// 区分局部状态的标记
const flag = Symbol('User') as InjectionKey<string>
/**
* 注册局部的当前登录用户的状态
* @returns 操作方法和用户状态
*/
export const regUserState = () => {
// 定义内部成员
// 内部使用的用户状态
const user = reactive<User>({
name: '没有登录',
isLogin: false,
power: {
modules: []
}
})
// 登录用的函数,仅示意,不涉及细节
const login = () => {
// 模拟登录
user.name = '张三'
user.isLogin = true
user.power.modules.length = 0
user.power.modules = [...[1,2,3]]
}
// 模拟退出,仅示意
const logout = () => {
// 模拟退出
user.name = '已经退出'
user.isLogin = false
user.power.modules.length = 0
}
// 定义用户的状态
const state = defineStore<UserState>(flag, {
user: readonly(user)
})
// 返回用户的状态和操作方式
return {
login,
logout,
state
}
}
/**
* 子组件获取状态的函数
* @returns 当前登录的用户状态(只读副本)
*/
export const getUserState = (): UserState => {
return useStoreLocal<UserState>(flag)
}
-
User、UserState 定义两个个简单的类型,仅作为示例。
-
Symbol 使用 Symbol 作为标识,不用担心重名问题。
-
私有成员 用户状态(user)和操作方法(login和logout)都是私有的形式,不通过状态返回。 操作方法通过创建状态的函数返回给父组件,父组件决定交给具体执行的组件,这样可以控制使用范围。
-
用户状态的只读副本 使用 defineStore 的 “自定义” 方式创建了一个状态,传入 readonly 只读副本,这样其他组件获取状态的时候,只能读取信息,无法改变状态,提高安全性。 状态返回的是 user 的只读副本(
readonly(user)
),这样设置状态结构,安全性可以更高一些,当然不可能绝对安全,只是尽力而为。 -
明确创建和获取 使用 regUserState() 创建状态;使用 getUserState() 获取状态。
父组件里面引入
这里的父组件指的是 App.vue ,这是仅次于 main 的层级,在这里创建的状态,下面的组件都可以访问到。在 App.vue 里面创建状态,是为了得到操作函数后,可以交给子组件,而在 main 里面无法访问子组件。
然后在App.vue 里面加载用户相关的组件,比如显示用户的组件、登录表单组件等,把相应的操作函数传入对应的组件。
- App.vue
// 创建状态
import { regUserState } from './store-nf/state-user'
// 用户组件
import user from './components/user.vue'
// 创建用户状态,获取操作方法
const {
login,
logout
} = regUserState()
然后在template 里面加载需要的组件,传入需要的函数。
<!--用户组件-->
<user :login="login" :logout="logout"></user>
登录组件里使用
设置props,接收 App.vue 传入的函数,显示需要的信息。
- ./components/user.vue
// 获取状态
import { getUserState } from '../store-nf/state-user'
const props = defineProps<{
login: () => void,
logout: () => void
}>()
// 头像网址
const circleUrl = 'xxx'
// 只读的用户状态
const { user } = getUserState()
- 定义 props,接收父组件传入的函数,以便于执行登录、退出的功能。
- 获取状态,只读的不能修改,强行修改的话会被拦截,同时给警告。
<span v-if="user.isLogin">
<!--已经登录,显示头像和退出按钮-->
<el-avatar size="" shape="square" :src="circleUrl" />
<br>
欢迎 {{ user.name }}
<hr>
<el-button type="" @click="logout">退出</el-button>
</span>
<span v-else>
<!--没有登录-->
<el-button type="" @click="login">登录</el-button>
</span>
一般放在网站的右上角,没有登录的时候,显示“登录”按钮,登录后切换为用户头像和相关信息,以及退出按钮。这里只是做一个简单示例。
其他组件里面使用
其他组件只能获得用户状态的只读的副本,安全可靠不用担心用户信息被意外修改。
// 获取状态
import { getUserState } from '../store-nf/state-user'
// 只读的用户状态
const { user } = getUserState()
// 不能直接修改,会被拦截
// user.name = 'ddd'