【问题标题】:x instanceof class type passed through parameterx instanceof 类类型通过参数传递
【发布时间】:2021-09-02 14:45:56
【问题描述】:

我试图确定一个变量是否是函数中参数类型类的子类。这样做是为了确保系统使用基于角色的方法进行身份验证。

我有一个脚本,其中包含用户类型列表和分配类的工厂方法:

abstract class UserType {}

abstract class EmpRoleType extends UserType {}
abstract class ClientRoleType extends UserType {}

class EmpSupport extends EmpRoleType {}
class ClientSupport extends ClientRoleType {}

function getUserType(role: string): UserType | null {
  switch(role) {
    case 'EmpSupport': 
      return EmpSupport;
    case 'ClientSupport': 
      return ClientSupport;

    default: 
      return null;
  }
}

要查找用户是否使用正确的角色进行身份验证,我有这个功能:

 // Pass in parent type of role to allow. Eg. EmpRoleType
function allowPermission(allowParentRole: UserType, userRole: UserType) {
  return userRole instanceof allowParentRole;
}

但是这给了我一个错误:The right-hand side of an 'instanceof' expression must be of type 'any' or of a type assignable to the 'Function' interface type.

即使尝试从 getUserType 实例化一个类也会失败:

const x = new getUserType('EmpSupport')();

错误:'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.

更新:

使用 Captain-yossarian 回答:假设我不知道字符串的值,我如何找到将字符串变量映射到其中一个类?例如。

get UserType(): UserType | null {
    let storageUserType = localStorage.getItem(AuthService.authStorageItems.userType);
    if (!storageUserType) {
      return null;
    }

    try {
      return ClassMap[storageUserType]; // or
      return GetUserType(storageUserType);
    } catch (error) {
      return null;
    }
  }

【问题讨论】:

    标签: typescript class


    【解决方案1】:

    首先我们要知道typeof UserType UserType是两种不同的类型。

    第一个类型typeof UserType 是类构造函数的类型。 第二类UserTypeUserType类实例的类型。

    在这种情况下,我认为最好使用HashMap数据结构而不是switch。 考虑一下:

    abstract class UserType { }
    
    abstract class EmpRoleType extends UserType { }
    abstract class ClientRoleType extends UserType { }
    
    class EmpSupport extends EmpRoleType { }
    class ClientSupport extends ClientRoleType { }
    
    const ClassMap = {
        'EmpSupport': class EmpSupport extends EmpRoleType { },
        'ClientSupport': class ClientSupport extends ClientRoleType { }
    } as const;
    

    在这种情况下,我们不再需要switch。 现在我们可以定义getUserType函数:

    type Values<T> = T[keyof T];
    
    function hasProperty<Obj>(obj: Obj, prop: any): prop is keyof Obj {
      return Object.prototype.hasOwnProperty.call(obj, prop);
    }
    
    const withMap = <
      Key extends PropertyKey,
      Klass extends new () => any,
      ClassMap extends Record<Key, Klass>
    >(
      classMap: ClassMap
    ) => {
      return function GetValue<Role extends PropertyKey>(
        role: Role
      ): Values<ClassMap> | null {
        if (hasProperty(classMap, role)) {
          return classMap[role];
        }
    
        return null;
      };
    };
    

    现在我们定义变量以使用正确的映射访问该函数

    const GetUserType = withMap(ClassMap);

    现在我们可以通过将字符串传递给函数来获取类类型。

    const userType = GetUserType('EmpSupport'); // typeof EmpSupport

    注意:如果将键传递给函数,它将返回 null

    例如。 GetUserType('bad entry');

    如果您想创建GetUserType retuen 值的实例,您需要使用额外的括号

    const userType = new (GetUserType('EmpSupport'))(); // EmpSupport
    

    allowPermission 应该是一个 typeguard,写成如下:

    function allowParentPermission<Allowed extends typeof UserType>(
      AllowParentRole: Allowed,
      userRole: unknown
    ): userRole is InstanceType<Allowed> {
      return userRole instanceof AllowParentRole;
    }
    

    按照惯例,所有类都应该大写,这就是我写AllowParentRole而不是allowParentRole的原因

    完整代码:

    abstract class UserType { }
    
    abstract class EmpRoleType extends UserType { }
    abstract class ClientRoleType extends UserType { }
    
    class EmpSupport extends EmpRoleType { }
    class ClientSupport extends ClientRoleType { }
    
    const ClassMap = {
        'EmpSupport': class EmpSupport extends EmpRoleType { },
        'ClientSupport': class ClientSupport extends ClientRoleType { }
    } as const;
    
    type Values<T> = T[keyof T];
    
    function hasProperty<Obj>(obj: Obj, prop: any): prop is keyof Obj {
      return Object.prototype.hasOwnProperty.call(obj, prop);
    }
    
    const withMap = <
      Key extends PropertyKey,
      Klass extends new () => any,
      ClassMap extends Record<Key, Klass>
    >(
      classMap: ClassMap
    ) => {
      return function GetValue<Role extends PropertyKey>(
        role: Role
      ): Values<ClassMap> | null {
        if (hasProperty(classMap, role)) {
          return classMap[role];
        }
    
        return null;
      };
    };
    
    function allowParentPermission<Allowed extends typeof UserType>(
      AllowParentRole: Allowed,
      userRole: unknown
    ): userRole is InstanceType<Allowed> {
      return userRole instanceof AllowParentRole;
    }
    

    Playground

    更新

    userRole is - 是type guards 的特殊语法

    更新 2

    abstract class UserType { }
    
    abstract class EmpRoleType extends UserType { }
    abstract class ClientRoleType extends UserType { }
    
    class EmpSupport extends EmpRoleType { }
    class ClientSupport extends ClientRoleType { }
    
    const ClassMap = {
        'EmpSupport': class EmpSupport extends EmpRoleType { },
        'ClientSupport': class ClientSupport extends ClientRoleType { }
    } as const;
    
    const hasProperty = <Obj,>(obj: Obj, prop: any)
        : prop is keyof Obj =>
        Object.prototype.hasOwnProperty.call(obj, prop);
    
    type Values<T> = T[keyof T]
    const withMap = <
        Key extends PropertyKey,
        Klass extends new () => any,
        ClassMap extends Record<Key, Klass>
    >(classMap: ClassMap) => {
        function foo<Role extends PropertyKey>(role: Role): Role extends keyof ClassMap ? ClassMap[Role] : Values<ClassMap> | null
        function foo<Role extends PropertyKey>(role: Role): Values<ClassMap> | null {
            const storageUserType = localStorage.getItem('any item you want');
    
            if (hasProperty(classMap, storageUserType)) {
                return classMap[storageUserType]
            }
    
            return null;
        }
        return foo
    }
    
    const getUserType = withMap(ClassMap)
    
    const foo = (str: string) => {
        const userType = getUserType(str); // EmpSupport
        if (userType) {
            const result = new userType()
        }
    }
    
    const result = getUserType('EmpSupport') //  typeof EmpSupport
    

    Playground 2

    【讨论】:

    • 您好,非常感谢!我不会想出这个解决方案!在 withMap 中,ClassMap 泛型之后的“,”(逗号)有什么作用?
    • 这是一个针对 TS 的特殊 hack。没有这个逗号 TS 是无法区分 JSX 和泛型的
    • 嗯,好的。还有为什么allowPermission返回类型中需要“userRole is”?
    • 进行了更新。这是类型保护的特殊语法typescriptlang.org/docs/handbook/…
    • 最后一个问题如何将字符串映射到ClassMap?如果我有一个字符串类型的变量,如何将其转换为类?
    【解决方案2】:

    在您的函数allowPermission 中,您在使用instanceof 的同时在两侧使用给定类型的实例,但这不是它的工作方式。 instanceof 用于检查object 是否属于type

    测试构造函数的原型属性是否出现在对象原型链的任何位置。返回值是一个布尔值。

    参考 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof

    这些都行,

    return userRole instanceof UserType;
    
    return userRole instanceof EmpSupport;
    
    return userRole instanceof ClientSupport;
    

    对于您的下一个问题,关于getUserType。在这里,您也在运行时创建一个类型的实例,但是如果不知道要创建什么类型的实例,就无法做到这一点,因为在运行时,没有打字稿。

    您可能应该在方法本身中更新类型,以便从那里获取实例,而不是按照您的方式进行,

    function getUserType(role: string): UserType | null {
      switch(role) {
        case 'EmpSupport': 
          return new EmpSupport();
        case 'ClientSupport': 
          return new ClientSupport();
    
        default: 
          return null;
      }
    }
    
    const x = getUserType('EmpSupport');
    

    【讨论】:

    • 不幸的是,这对我没有帮助,因为整个问题是在运行时验证父类型。我专门选择了类,因为它们存在于 javascript 中并且应该存在于运行时。我有什么困惑吗?你能为此建议一个替代实现吗?
    猜你喜欢
    • 1970-01-01
    • 2014-02-05
    • 2016-12-04
    • 2019-11-11
    • 2016-02-24
    • 2021-02-12
    • 2020-10-11
    • 1970-01-01
    相关资源
    最近更新 更多