【问题标题】:Restricting the type on function argument in Node.js and TypeScript在 Node.js 和 TypeScript 中限制函数参数的类型
【发布时间】:2021-03-18 18:16:27
【问题描述】:

处理 Node.js 项目并使用 TypeScript。

我正在尝试将函数参数类型限制为特定的基类。我是 Node 和 TypeScript 的新手,并且来自 C# 背景,因此可能不太了解 lang 的某些特征。

拿着这些sn-ps。

首先,我的班级声明

class DTO{
}

class userDTO extends DTO{
    @IsDefined({message:"Username required"})
    @Expose()
    @Length(1,10, {message:"min 1 max 10"})
    username:String;    
  }
  
class badDTO {
    name:String;
}

现在我将创建实例:

let user = new userDTO();
user.username = "My username";

let isUserDTO = user instanceof DTO; // Evaluates true

let bad = new badDTO();
bad.name = "Bob";

let isBadDTO = user instanceof DTO; // Evaluates false

这是我打算调用的方法的签名

export default function ValidateDTO(objToValidate:DTO, validateMissingProperties:boolean): Array<string>{
    return [];
}

最后,当我真正调用函数时。

let userErrors = ValidateDTO(user, true);

// Why is this allowed?
let badErr = ValidateDTO(bad, true);

我希望第二个 ValidateDTO 向我显示一个警告,但实际上并没有运行,因为 'bad' 不是上面 instanceOf 所证明的 DTO - 如果我尝试将字符串作为第二个参数传递,我会看到一个错误,这就是我希望将非 DTO 作为第一个参数传递。

谁能告诉我哪里出错了?如何限制传递给函数的对象类型。

也很乐意根据需要分享其他代码。不知道我可能还缺少什么。

【问题讨论】:

    标签: node.js typescript


    【解决方案1】:

    对此感到惊讶的不止你一个人。 :-) TypeScript 类型系统的关键之一是它是structural(基于结构),而不是nominal(基于名称)。只要某物具有必要的最小结构,即使它具有不同的祖先,它也可以匹配。这意味着 any 对象将被类型系统接受为您的 DTO 类型,因为您的 DTO 类型没有属性,因此所有对象都匹配它。

    这主要是一个功能,但有时您想禁用它。当您想要禁用它时,通常的方法是使用品牌属性:

    class DTO {
        __brand = "DTO" as const;
    }
    

    现在,只有具有 __brand 属性和值 "DTO" 的对象才会被允许,而类型系统需要 DTO 对象。


    这是一个完整的示例,其中进行了一些小的更改,以更符合 JavaScript/TypeScript 命名约定并提供问题代码中缺少的一些位(大概是为了保持简短!:-)):

    class DTO {
        __brand = "DTO" as const;
    }
    
    class UserDTO extends DTO {
        /* Commenting these out as they're not relevant to the question.
        @IsDefined({message:"Username required"})
        @Expose()
        @Length(1,10, {message:"min 1 max 10"})
        */
        username: string;
        
        constructor(username: string) {
            super();
            this.username = username;
        }
    }
      
    class BadDTO {
        name: string = "";
    }
    
    function validateDTO(objToValidate: DTO, validateMissingProperties: boolean): string[] {
        return [];
    }
    
    // Okay
    validateDTO(new UserDTO("Joe"), true);
    
    // Disallowed by the type system
    validateDTO(new BadDTO(), false);
    

    Playground link


    旁注 2:在该示例中,我向 UserDTO 添加了一个构造函数,该构造函数初始化了 username 属性。当您想使用构造函数参数初始化实例属性时,TypeScript 有一个简写,这在功能上与我的示例中的 UserDTO 相同:

    class UserDTO extends DTO {
        /* Commenting these out as they're not relevant to the question.
        @IsDefined({message:"Username required"})
        @Expose()
        @Length(1,10, {message:"min 1 max 10"})
        */
        //−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− note no `username` declaration here
        
        constructor(public username: string) {
        //          ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− note adding `public`
            super();
            // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− note no code here to do the
            // initialization; it's implicit in the `public` declaration above
        }
    }
    

    你使用哪个是风格问题。

    【讨论】:

    • 我喜欢你的解释——谢谢!当我尝试使用您的代码示例时,我在 VS Code 中收到警告,即在“as”字和“';' 上找不到名称 'as'”预期”在 const 字上时。也尝试过复制/粘贴。
    • 如果我放弃'as const',它就像你描述的那样工作。非常感谢 - 这是规范的最佳做法吗?
    • @DarrenWainwright - 我已经用一个完整的例子更新了答案。您需要 as const 以便它是一个编译时常量值,而不仅仅是一个可以在运行时更改其值的字符串属性。 :-) 是的,对于您关心这一点的情况,使用品牌是正常的做法,但在可能的情况下避免关心也是正常的做法。 :-D
    • T.K Crowder - 再次感谢您。如果我将“as const”留在原处,我将无法编译它。只是得到曲线。我正在使用 Node 14.14 和 TypeScript 4.0.5
    • 啊,好酷。感谢您提供更多信息和附注。超级有用
    猜你喜欢
    • 2023-02-22
    • 1970-01-01
    • 2023-01-20
    • 2021-06-27
    • 2016-10-28
    • 2020-11-10
    • 2021-12-15
    • 1970-01-01
    • 2020-01-19
    相关资源
    最近更新 更多