You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改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"]);

核心逻辑说明

  1. Tail类型:提取权限字符串去掉第一个段的部分,用于判断后缀是否重复
  2. HasDuplicate类型:检查当前权限在联合类型中是否存在后缀重复的其他权限
  3. FullCamel类型:将完整权限路径转换为驼峰格式,用于生成区分重复的键
  4. ExtractPermissionName类型:根据是否存在重复后缀,动态选择生成短键或完整路径键
  5. 运行时匹配:在reduce中判断是否存在重复后缀,生成与类型一致的属性键

内容的提问来源于stack exchange,提问作者Sirwan Afifi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 16:26:00