【问题标题】:How to map type of property to a constructor type如何将属性类型映射到构造函数类型
【发布时间】:2022-01-18 01:00:56
【问题描述】:

我正在尝试将属性的类型映射到其对应的构造函数类型,所以 string 应该变成 StringConstructornumber 应该变成 NumberConstructor...

这是我目前得到的,但智能感知显示 unknown 用于 test.name?.typetest.age?.type

interface IPerson {
  name?: string
  age?: number
}

const test = {
  name: { type: String },
  age: { type: Number },
} as { [key in keyof IPerson]: {
  type: IPerson[key] extends string
    ? StringConstructor
    : IPerson[key] extends number
      ? NumberConstructor
      : unknown
}}

console.log(test.name?.type) console.log(test.age?.type)

console.log(test.name?.type)
console.log(test.age?.type)

我需要更改什么才能正确映射type

TypeScript REPL 的 URL: https://www.typescriptlang.org/fr/play?strictNullChecks=false#code/JYOwLgpgTgZghgYwgAgJIEETALZwDbIDeAUMsiHNhAFzIDOYUoA5qfcAF43kCu2ARtGIBfYsVCRYiFKgAK0OgHsQRNhSq0GTEKzJxmEAPy0QfQVDYAHCGGNpMOfCLFgAnteQBZOJesATABV3CAAeAIA+ZABeVTIAbQBpZFBkAGsIV0UYZACAXVoSMjI3a1oAxNzkCAAPSBA-OnpGFmQAH2QeeogYUAg-NiLkQ2QAZWadAGFlLR4EMEULQbIyiqraiHrG0wFoNo6unpA+gaXhgDkzaCmQGbmFk8GVhMqauobkRX4AKwg5h9OiMgABQASielVESyWJggADchGRRKJiAhpmAqlAoAtorFyJRuIRkCVuGNtMxkMIADRsfQEonBWgXHZQCnUsjWMAFemlZAAeW+v3RVOQAHoReQ4dBKRisVAAIQiZBwRreXx9ILWEJyBTKcJiVE3RR4CAAOjwimYQOgspN6lNxJBovFpJY11u8wsBqUxrNFqtmIWJtphhNDqdyCZ5jdjFmHpR0yNpvNlutgY5IbDYol8Kg0tT8v1aPIigAogGWTFCniNIDibQXTpWTSDFy6xHLiyqVYbK2GXyBXMlY1QdFIhgsLgCF3hEOvD5-BrQtqoEoQHr44afcmgSBS+XbfjQ8FHVmG8xo1BY-cvYnfZbd2WbcGj9YT+LI1c0Ze7p6E1u-Q++7pi+EBvsCjpRGODiTsQQA

【问题讨论】:

  • 请注意,您的key 是一个type 参数,代表一个keylike type;它不是虚拟键 name。因此,将其命名为类型参数(一个或两个大写字符,如K,或者最坏的情况下为大写类型名称,如Key)而不是键名更为传统。

标签: typescript typescript-typings


【解决方案1】:

假设你启用了the --strictNullChecks compiler option,那么IPersonnameage属性的类型不是stringnumber;相反,它们是string | undefinednumber | undefinedOptional properties 自动将the undefined type 添加到他们的域中(如果您启用the --exactOptionalPropertyTypes compiler option,这有点复杂,但如果您阅读可选属性,它仍然可以包含undefined)。并且string | undefined 没有扩展string,所以一切都落入unknown,你很伤心。

也许处理这个问题的最方便的方法是只测试extends string | undefined 而不是extends string,因为string extends string | undefined 是真的:

const test: { [K in keyof IPerson]: {
  type: IPerson[K] extends string | undefined ? StringConstructor
  : IPerson[K] extends number | undefined ? NumberConstructor
  : unknown
} }
  = {
  name: { type: String },
  age: { type: Number },
};

/* const test: {
    name?: {
        type: StringConstructor;
    } | undefined;
    age?: {
        type: NumberConstructor;
    } | undefined;
} */

请注意,还有更通用的解决方案,它们会将string | number 类型的属性转换为StringConstructor | NumberConstuctor 类型的值;也就是说,他们会将distribute你的类型操作超过union types;或采用[string, StringConstructor] | [number, NumberConstructor] | ... 之类的映射类型并使用它以编程方式转换您的类型的解决方案,以便您只需添加[boolean, BooleanConstructor] 之类的新元素就可以了。但是所有这些都超出了所问问题的范围,所以我不会为这个问题深入探讨。

Playground link to code

【讨论】:

  • 添加undefined 工作正常。但是,使用这种方法我很难与object 进行比较。每当我定义type: Object 时,我希望打字稿强制使用返回该对象实例的工厂,例如type: Object as () => IAnimal。我创建了一个新的复制品。如您所见,error.pet?.type 仍然是 () => IAnimal 而不是 never。我怎样才能解决这个问题? (我更新了原来的 REPL 链接
  • 我不明白你在这里向我展示了什么,抱歉。您是否故意关闭--strictNullChecks(如果是,为什么?)您是否出于某种原因想要 never(如果是,为什么?)。您可以使用tsplay.dev 缩短游乐场链接,这样您就不需要修改您的问题(可能应该保持以前的方式,因为点击该链接的人会看到与您的问题代码不同的内容)。
  • 所以在我更新的示例中,IPerson 有一个属性pet,其类型为IAnimal。在我的对象定义中,我希望将其强制为{ type: Object as () => IAnimal },因为它在运行时用于验证。仅当属性类型为 any 时,才应允许 { type: Object }。如果该属性不是stringnumberboolean、...any,则结果应为() => IAnimal
  • 所以问题似乎是,Object 对于这个用例来说不够严格。 const test: () => IAnimal = Object 有效,所以我必须找到一种方法使 Object 更具限制性,或者使 MappedType 类型更具体,而不是检查 object
【解决方案2】:

我很确定问题在于IPerson.nameIPerson.age 的类型都是可选的。因此,您无法测试该属性是否扩展了字符串或数字,因为它实际上扩展了(string | undefined)(number | undefined)。因此,您的嵌套三元组总是会评估为unknown

要解决此问题,您可以更改 IPerson 的界面以同时具有两个属性

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-16
    • 2020-05-01
    • 2022-07-06
    • 1970-01-01
    • 2012-09-23
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多