【问题标题】:How to type a function argument with a function argument type in a nested object in TypeScript?如何在 TypeScript 的嵌套对象中使用函数参数类型键入函数参数?
【发布时间】:2019-09-30 20:43:24
【问题描述】:

我正在尝试键入一个函数,其中应从嵌套对象中的值推断出参数类型。如何推断深层嵌套对象中的函数参数类型?

例子:

export enum Role {
  USER = 'user',
  ADMIN = 'admin',
  OWNER = 'owner',
  PRIMARY_OWNER = 'primaryOwner',
}

// Add as needed. Formatted as 'resource:action'?
export type Ability =
  | 'users:create'
  | 'users:edit'
  | 'reports:view'
  | 'settings:view';

type StaticAbilities = readonly Ability[];

type DynamicAbility = (data: any) => boolean;

type DynamicAbilities = { readonly [key in Ability]?: DynamicAbility };

export type Abilities = {
  readonly [R in Role]?: {
    readonly static?: StaticAbilities;
    readonly dynamic?: DynamicAbilities;
  }
};

/**
 * A configuration object containing allowed abilities for specific roles.
 */
export const ABILITIES: Abilities = {
  user: {
    dynamic: {
      // THIS IS AN EXAMPLE OF DYNAMIC RULES
      'users:edit': ({
        currentUserId,
        userId,
      }: {
        /** Current users ID */
        currentUserId: string;
        /** User ID trying to be edited */
        userId: string;
      }) => {
        if (!currentUserId || !userId) return false;
        return currentUserId === userId;
      },
    },
  },
  admin: {
    static: ['reports:view', 'settings:view'],
  },
  owner: {
    static: ['reports:view', 'settings:view'],
  },
  primaryOwner: {
    static: ['reports:view', 'settings:view'],
  },
};

export const can = ({
  role,
  ability,
  data,
}: {
  role: Role;
  ability: Ability;
  data?: any;
}): boolean => {
  const permissions = ABILITIES[role];

  // Return false if role not present in rules.
  if (!permissions) {
    return false;
  }

  const staticPermissions = permissions.static;

  // Return true if rule is in role's static permissions.
  if (staticPermissions && staticPermissions.includes(ability)) {
    return true;
  }

  const dynamicPermissions = permissions.dynamic;

  if (dynamicPermissions) {
    const permissionCondition = dynamicPermissions[ability];

    // No rule was found in dynamic permissions.
    if (!permissionCondition) {
      return false;
    }

    return permissionCondition(data);
  }

  // Default to false.
  return false;
};

给定一个特定的roleability,我想在can() 中键入data 作为ABILITIES 中定义的函数参数类型(如果存在)。如果它不存在,那么我不希望需要data

当角色为Role.USER 且能力为'users:edit' 时,我希望data 的类型是{ currentUserId: string; userId: string } 的必需类型。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    如果ABILITIES 是使用对象文字的实际类型(不仅仅是Abilities),您可以使用一些条件类型魔术来提取适当的参数类型。我们可以使用一个额外的函数来帮助编译器推断出正确的类型。

    export enum Role {
        USER = 'user',
        ADMIN = 'admin',
        OWNER = 'owner',
        PRIMARY_OWNER = 'primaryOwner',
    }
    
    // Add as needed. Formatted as 'resource:action'?
    export type Ability =
        | 'users:create'
        | 'users:edit'
        | 'reports:view'
        | 'settings:view';
    
    type StaticAbilities = readonly Ability[];
    
    type DynamicAbility = (data: any) => boolean;
    
    type DynamicAbilities = { readonly [key in Ability]?: DynamicAbility };
    
    export type Abilities = {
        readonly [R in Role]?: {
            readonly static?: StaticAbilities;
            readonly dynamic?: DynamicAbilities;
        }
    };
    
    function createAbilities<A extends Abilities>(a: A) {
        return a;
    }
    export const ABILITIES = createAbilities({
        user: {
            dynamic: {
                // THIS IS AN EXAMPLE OF DYNAMIC RULES
                'users:edit': ({
                    currentUserId,
                    userId,
                }: {
                    /** Current users ID */
                    currentUserId: string;
                    /** User ID trying to be edited */
                    userId: string;
                }) => {
                    if (!currentUserId || !userId) return false;
                    return currentUserId === userId;
                },
            },
        },
        admin: {
            static: ['reports:view', 'settings:view'],
        },
        owner: {
            static: ['reports:view', 'settings:view'],
        },
        primaryOwner: {
            static: ['reports:view', 'settings:view'],
        },
    });
    type ExtractDynamicParameter<R extends Role, A extends Ability> = typeof ABILITIES[R] extends { dynamic: Record<A, (p: infer P) => boolean> } ? { data : P } : { data?: undefined}
    export const can = <R extends Role, A extends Ability>({
        role,
        ability,
        data,
    }: {
        role: R;
        ability: A;
    } & ExtractDynamicParameter<R, A>): boolean => {
        const permissions = ABILITIES[role as Role] as Abilities[Role]; // Needed assertions 
    
        // Return false if role not present in rules.
        if (!permissions) {
            return false;
        }
    
        const staticPermissions = permissions.static;
    
        // Return true if rule is in role's static permissions.
        if (staticPermissions && staticPermissions.includes(ability)) {
            return true;
        }
    
        const dynamicPermissions = permissions.dynamic;
    
        if (dynamicPermissions) {
            const permissionCondition = dynamicPermissions[ability];
    
            // No rule was found in dynamic permissions.
            if (!permissionCondition) {
                return false;
            }
    
            return permissionCondition(data);
        }
    
        // Default to false.
        return false;
    };
    
    can({ role: Role.USER, ability: "users:edit", data: { currentUserId: "", userId: "" } }) // ok 
    can({ role: Role.USER, ability: "users:edit", data: {} }) // err
    can({ role: Role.USER, ability: "users:edit" }) // err
    

    【讨论】:

    • 我想让can({ role: Role.USER, ability: 'users:edit' }); 在需要时因为缺少data 而失败。我认为这样做的方法是执行某种函数重载?
    • @erictaylor 将答案更改为像上面的请求一样工作。
    猜你喜欢
    • 1970-01-01
    • 2019-12-04
    • 2022-01-23
    • 1970-01-01
    • 2022-01-05
    • 2013-11-03
    • 2020-06-10
    • 2020-12-08
    • 2022-07-23
    相关资源
    最近更新 更多