Vue3+Pinia中调用ref对象的hasPermission函数报错求助
问题描述
在基于Vue3、TypeScript和Pinia的项目中,在_Layout.vue组件调用currentUser.value.hasPermission()时,出现错误:
Uncaught (in promise) TypeError: currentUser.value.hasPermission is not a function
核心疑问:是否是ref或Pinia存储的特性,导致无法调用存储对象上的函数?
相关代码
1. Pinia Store 代码 (@/stores/main-store.ts)
import { defineStore } from "pinia"; import type { IUser } from "@/lib/user"; export const useStore = defineStore({ id: "main", state: () => ({ currentUser: {} as IUser, currentTenant: null as string | null, theme: null as string | null, }), });
2. 用户模型代码 (@/lib/user.ts)
import { getToken } from "@/lib/auth"; export interface IUser { id: string; tenants: IUserTenant[]; hasPermission: ( tenantIdentifier: string|null, permission: IUserPermission ) => boolean; } export interface IUserTenant { tenantIdentifier: string; tenantName: string; permissions: IUserPermission[]; } export interface IUserPermission { permissionZone: PermissionZone; permissionType: PermissionType; } export enum PermissionZone { Me, Tenants, Users, Contacts, } export enum PermissionType { List, Read, Create, Update, Delete, } const API_EP = import.meta.env.VITE_API_ENDPOINT; export class User implements IUser { public static async getCurrentUser(): Promise<IUser> { const token = await getToken(); const response = await fetch(`${API_EP}/user`, { headers: { Authorization: `Bearer ${token}`, }, mode: "cors", }); if (response.ok) { return (await response.json()) as User; } // TODO Define Error response model and parse for message throw new Error("Unable to retrieve current user."); } public id!: string; public tenants: IUserTenant[] = []; public hasPermission(tenantIdentifier: string | null, permission: IUserPermission): boolean { return this.tenants.some( (t) => t.tenantIdentifier === tenantIdentifier && t.permissions.some( (p) => p.permissionZone === permission.permissionZone && p.permissionType === permission.permissionType ) ); } }
3. Layout组件代码 (@/views/_Layout.vue 的 <script> 部分)
import { onMounted, ref } from "vue"; import { useStore } from "@/stores/main-store"; import { storeToRefs } from "pinia"; import { Navigation } from "@/lib/navigation"; import type { INavigationItem } from "@/lib/navigation"; const store = useStore(); const { currentUser, currentTenant } = storeToRefs(store); const navItems = ref<INavigationItem[]>(); onMounted(async () => { navItems.value = Navigation.filter((i) => currentUser.value.hasPermission( currentTenant.value, i.permission ) ); });
补充:路由中currentUser的赋值逻辑 (@/router/index.ts)
import { createRouter, createWebHistory } from "vue-router"; import Layout from "@/views/_Layout.vue"; import { AuthenticationGuard } from "vue-auth0-plugin"; import { User } from "@/lib/user"; import { useStore } from "@/stores/main-store"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { name: "default", path: "/", component: Layout, beforeEnter: async (to, from) => { // Handle login const authed = await AuthenticationGuard(to, from); useStore().$patch({ currentUser: await User.getCurrentUser(), }); if (authed && to.name === "default") await router.push({ name: "dashboard" }); return authed; }, children: [ { name: "dashboard", path: "", component: () => import("@/views/DashboardPage.vue"), }, ], }, ], }); export default router;
问题原因与解决方法
核心原因
不是ref或Pinia的特性导致的问题,而是API返回的JSON对象只是普通对象,没有继承User类的方法。你在User.getCurrentUser()里把response.json()断言为User类型,但实际上JSON解析出来的对象不会包含类的原型方法hasPermission,只是拥有相同属性的普通对象。
另外,Pinia的state是普通对象,即使你一开始把currentUser声明为IUser类型,当用普通对象替换后,它也不会有类方法。
解决方法
方法1:将API返回的普通对象转换为User实例
修改User.getCurrentUser()方法,把JSON返回的对象转换成User类的实例,这样就能继承hasPermission方法:
public static async getCurrentUser(): Promise<User> { const token = await getToken(); const response = await fetch(`${API_EP}/user`, { headers: { Authorization: `Bearer ${token}`, }, mode: "cors", }); if (response.ok) { const userData = await response.json(); // 创建User实例并赋值属性 const user = new User(); user.id = userData.id; user.tenants = userData.tenants; return user; } throw new Error("Unable to retrieve current user."); }
方法2:把hasPermission改成独立函数(无需类实例)
如果不想用类实例,可以把权限判断逻辑抽成独立函数,不需要依赖User类的this:
// @/lib/user.ts 中添加独立函数 export function hasPermission( user: IUser, tenantIdentifier: string | null, permission: IUserPermission ): boolean { return user.tenants.some( (t) => t.tenantIdentifier === tenantIdentifier && t.permissions.some( (p) => p.permissionZone === permission.permissionZone && p.permissionType === permission.permissionType ) ); }
然后在Layout组件中调用:
import { hasPermission } from "@/lib/user"; // ... onMounted(async () => { navItems.value = Navigation.filter((i) => hasPermission(currentUser.value, currentTenant.value, i.permission) ); });
方法3:在Pinia Store中添加action处理权限判断
也可以在Pinia Store里添加一个action来封装权限判断逻辑,避免直接在组件中依赖用户对象的方法:
// @/stores/main-store.ts export const useStore = defineStore({ id: "main", state: () => ({ currentUser: {} as IUser, currentTenant: null as string | null, theme: null as string | null, }), actions: { hasPermission(permission: IUserPermission): boolean { return this.currentUser.tenants?.some( (t) => t.tenantIdentifier === this.currentTenant && t.permissions.some( (p) => p.permissionZone === permission.permissionZone && p.permissionType === permission.permissionType ) ) || false; } } });
然后在组件中调用:
onMounted(async () => { navItems.value = Navigation.filter((i) => store.hasPermission(i.permission) ); });
内容的提问来源于stack exchange,提问作者Hades
相关产品推荐
相关产品推荐

