Разрушение универсального типа

У меня есть функция, в которой некоторые свойства ввода зависят от значения одного из его свойств. Итак, я определил тип следующим образом, и он работает, как ожидалось:

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: Argument of type 'Pick<CheckRolePermissionsArgs

, "perform" | "sustain" | Exclude<keyof (P extends OrganizationPermission | WorkspacePermission.UPDATE ? RolePermission[P] : Record<...>), "role">>' is not assignable to parameter of type 'CheckPermissionsArgs<CheckRolePermissionsArgs

["perform"]>'.   Type 'Pick<CheckRolePermissionsArgs

, "perform" | "sustain" | Exclude<keyof (P extends OrganizationPermission | WorkspacePermission.UPDATE ? RolePermission[P] : Record<...>), "role">>' is not assignable to type 'CheckRolePermissionsArgs

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

Как мне провести рефакторинг этих типизаций, чтобы избежать использования аргумента props во втором примере?


person Marcos Passos    schedule 22.05.2021    source источник


Ответы (1)


Я думаю, это из-за контравариантности, но это только мое предположение. Не стесняйтесь критиковать меня.

См. этот ответ.

Рассмотрим следующий пример:


/**
 * 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


площадка

person captain-yossarian    schedule 22.05.2021
comment
У меня нет такой возможности. Я просто упростил для краткости, но реальный случай включает компонент реакции, поэтому у меня не может быть второго параметра, как было предложено. - person Marcos Passos; 22.05.2021