基于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'
源码
在线演示
转载自:https://juejin.cn/post/7238406406944243749