【问题标题】:Destructuring a generic type解构泛型类型
【发布时间】:2021-08-11 10:22:33
【问题描述】:

我有一个函数,其中某些输入属性取决于其中一个道具的值。所以我定义了一个类型如下,它按预期工作:

enum OrganizationPermission {
    UPDATE = 'organization:update',
    INVITE = 'organization:invite',
}

enum WorkspacePermission {
    UPDATE = 'workspace:update',
    INVITE = 'workspace:invite',
}

enum OrganizationRole {
    MANAGER = 'manager'
}

enum WorkspaceRole {
    MANAGER = 'manager'
}

type RolePermission = {
    [OrganizationPermission.UPDATE]: {
        organizationRole: OrganizationRole,
    },
    [OrganizationPermission.INVITE]: {
        organizationRole: OrganizationRole,
    },
    [WorkspacePermission.UPDATE]: {
        workspaceRole: WorkspaceRole,
    },
}

type Permissions = WorkspacePermission | OrganizationPermission;

type CheckPermissionsArgs<P extends Permissions> = {
    perform: P,
    sustain?: boolean,
} & (P extends keyof RolePermission ? RolePermission[P] : Record<string, never>);

function checkPermissions<P extends Permissions>(props: CheckPermissionsArgs<P>): void {
    // omitted for brevity
}

当我必须扩展这个道具时就会出现问题,如下例所示:

type CheckRolePermissionsArgs<P extends Permissions> = CheckPermissionsArgs<P> & {
    role: string[],
}

function checkRolePermissions<P extends Permissions>({role, ...props}: CheckRolePermissionsArgs<P>): void {
    // Typing error
    checkPermissions(props);
}

Typescript 抱怨说:

TS2345:'Pick 类型的参数

, "执行" | “维持” | Exclude), "role">>' 不可分配给类型为 'CheckPermissionsArgs 的参数

[“执行”]>'。键入'Pick

, "执行" | “维持” | Exclude), "role">>' 不可分配给类型 'CheckRolePermissionsArgs

["perform"] 扩展了 OrganizationPermission | WorkspacePermission.UPDATE ? RolePermission[CheckRolePermissionsArgs<...>["perform"]] : Record<...>'

如何重构这些类型以避免在第二个示例中强制转换 props 参数?

【问题讨论】:

    标签: typescript generics typing


    【解决方案1】:

    我认为这是因为 逆变,但这只是我的猜测。欢迎批评我。

    请参阅this 答案。

    考虑下一个例子:

    
    /**
     * VARIANCE
     */
    declare var y: Omit<CheckRolePermissionsArgs<WorkspacePermission.UPDATE>, 'role'>
    
    let x: CheckPermissionsArgs<WorkspacePermission.UPDATE> = y // ok
    y = x // ok
    checkPermissions(x)
    
    

    因为checkRolePermissions 是高阶函数:

    function checkRolePermissions<P extends Permissions_>(props: CheckRolePermissionsArgs<P>): void {
      // Typing error
      const { role, ...rest } = props
      type Keys = keyof typeof rest // "perform" | "sustain" | Exclude<keyof (P extends keyof RolePermission ? RolePermission[P] : Record<string, never>), "role">
      checkPermissions(rest);
    }
    

    TS 无法识别rest 类型,因为它在这个地方有点动态类型。它只在运行时知道。

    我认为这里最好的解决方案是拆分CheckPermissionsArgs 类型。

    enum OrganizationPermission {
      UPDATE = 'organization:update',
      INVITE = 'organization:invite',
    
    }
    
    enum WorkspacePermission {
      UPDATE = 'workspace:update',
      INVITE = 'workspace:invite',
    }
    
    
    enum OrganizationRole {
      MANAGER = 'manager'
    }
    
    enum WorkspaceRole {
      MANAGER = 'manager'
    }
    
    type RolePermission = {
      [OrganizationPermission.UPDATE]: {
        organizationRole: OrganizationRole,
      },
      [OrganizationPermission.INVITE]: {
        organizationRole: OrganizationRole,
      },
      [WorkspacePermission.UPDATE]: {
        workspaceRole: WorkspaceRole,
      }
    }
    
    type Permissions_ = WorkspacePermission | OrganizationPermission;
    
    
    type CheckPermissionsArgs<P extends Permissions_> = {
      perform: P,
      sustain?: boolean,
    };
    
    type Union<P extends Permissions_> = P extends keyof RolePermission ? RolePermission[P] : Record<string, never>
    
    function checkPermissions<P extends Permissions_>(props: CheckPermissionsArgs<P>, data: Union<P>) {
      // this object has exactly the same type as you have in your question
      const merged = { ...props, ...data } 
    }
    
    
    type CheckRolePermissionsArgs<P extends Permissions_> = CheckPermissionsArgs<P> & {
      role: string[],
    }
    
    
    
    function checkRolePermissions<P extends Permissions_>({ role, ...rest }: CheckRolePermissionsArgs<P>) {
      // Now, TS is able to infer the type
      return (data: Union<P>) => checkPermissions(rest, data);
    
    }
    
    
    checkRolePermissions({
      perform: OrganizationPermission.UPDATE,
      sustain: true,
      role: ['sdf']
    })({
      organizationRole: OrganizationRole.MANAGER,
    }) // ok
    
    
    checkRolePermissions({
      perform: WorkspacePermission.INVITE,
      sustain: true,
      role: ['sdf']
    })({
      organizationRole: OrganizationRole.MANAGER,
    }) // expected error
    
    
    

    Playground

    【讨论】:

    • 我没有这个选项。为了简洁起见,我只是简化了,但实际情况涉及一个反应组件,所以我不能像建议的那样有第二个参数。
    猜你喜欢
    • 1970-01-01
    • 2022-10-23
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-30
    相关资源
    最近更新 更多