如何修改TypeScript类型ExtractPermissionName处理重复权限段
权限Hook的类型生成重复段处理方案
问题背景
现有一个权限Hook,需要根据权限字符串生成属性键,存在以下需求和问题:
- 对于
Company.SendInvitation这类唯一后缀的权限,需生成isSendInvitationEnabled - 对于
["Company.Users.View", "Platform.Users.View"]这类后缀重复的权限,需生成isCompanyUsersViewEnabled和isPlatformUsersViewEnabled
当前类型定义会为重复后缀的权限生成相同的键(如isUsersViewEnabled),但运行时代码已正确处理重复,需修正类型定义以匹配实际行为。
原类型问题
原ExtractPermissionName类型总是去掉权限字符串的第一个段,导致重复后缀的权限生成相同的键:
type ExtractPermissionName<T> = T extends `${string}.${infer R}` ? R extends `${infer F}.${infer Rest}` ? `${Capitalize<F>}${ExtractPermissionName<Rest>}` : Capitalize<R> : T;
解决方案
我们需要让类型系统能够判断权限后缀是否存在重复,若重复则生成包含完整路径的键,否则保留短键。以下是修改后的完整类型定义和Hook实现:
type AllPermissions = "Company.SendInvitation" | "Company.Users.View" | "Platform.Users.View"; type PermissionOptions<P extends string = string, S extends string = string> = { prefix: P; suffix: S; }; // 获取权限字符串去掉第一个段后的部分 type Tail<T extends string> = T extends `${infer _}.${infer R}` ? R : T; // 判断联合类型中是否存在与当前权限后缀重复的其他权限 type HasDuplicate<U extends string, P extends string> = Exclude<U, P> extends never ? false : Tail<Exclude<U, P>> extends Tail<P> ? true : false; // 将完整权限路径转换为驼峰格式 type FullCamel<T extends string> = T extends `${infer F}.${infer R}` ? `${Capitalize<F>}${FullCamel<R>}` : Capitalize<T>; // 根据是否存在重复后缀,选择生成短键或完整路径键 type ExtractPermissionName<U extends string, P extends string> = HasDuplicate<U, P> extends true ? FullCamel<P> : Tail<P> extends `${infer F}.${infer Rest}` ? `${Capitalize<F>}${ExtractPermissionName<U, Rest>}` : Capitalize<Tail<P>>; // 生成最终的权限属性键 type PermissionKey< U extends string, P extends string, O extends Partial<PermissionOptions> = { prefix: "is"; suffix: "Enabled" } > = `${O["prefix"] extends string ? O["prefix"] : "is"}${ExtractPermissionName<U, P>}${ O["suffix"] extends string ? O["suffix"] : "Enabled" }`; const usePermissions = <P extends AllPermissions | AllPermissions[]>( permissions: P ) => { const perms: AllPermissions[] = Array.isArray(permissions) ? permissions : [permissions]; return perms.reduce((acc, perm) => { // 运行时判断是否存在重复后缀,生成对应键 const hasDuplicate = perms.some(p => p !== perm && p.split('.').slice(1).join('.') === perm.split('.').slice(1).join('')); const keySegments = hasDuplicate ? perm.split(".") : perm.split(".").slice(1); const key = `is${keySegments.map(s => s.charAt(0).toUpperCase() + s.slice(1)).join("")}Enabled`; return { ...acc, [key]: true }; }, {}) as { [K in P extends AllPermissions[] ? P[number] : P]: boolean } extends { [K in infer U]: boolean } ? Record<PermissionKey<U extends string ? U : never, U extends string ? U : never>, boolean> : never; };
验证效果
// 单个权限:类型为 { isUsersViewEnabled: boolean } const single = usePermissions("Company.Users.View"); // 无重复后缀的多权限:类型为 { isUsersViewEnabled: boolean; isSendInvitationEnabled: boolean } const multiple = usePermissions(["Company.Users.View", "Company.SendInvitation"]); // 有重复后缀的多权限:类型为 { isPlatformUsersViewEnabled: boolean; isCompanyUsersViewEnabled: boolean } const custom = usePermissions(["Platform.Users.View", "Company.Users.View"]);
核心逻辑说明
- Tail类型:提取权限字符串去掉第一个段的部分,用于判断后缀是否重复
- HasDuplicate类型:检查当前权限在联合类型中是否存在后缀重复的其他权限
- FullCamel类型:将完整权限路径转换为驼峰格式,用于生成区分重复的键
- ExtractPermissionName类型:根据是否存在重复后缀,动态选择生成短键或完整路径键
- 运行时匹配:在reduce中判断是否存在重复后缀,生成与类型一致的属性键
内容的提问来源于stack exchange,提问作者Sirwan Afifi
相关产品推荐
相关产品推荐

