将具有可选属性的类型强制为可索引类型

sna*_*che 5 typescript

尝试将具有可选属性的类型作为可索引类型传递时,Typescript 会引发错误:( Playground )

type Thing = {
    thing1?: string
    thing2?: string
    thing3?: number
}

const thing: Thing = {}

function processObject (obj: { [key: string]: string | number }): string {
    /* Generic object handler, not specifically for Thing */
    return "test"
}

console.assert(processObject(thing) === "test")
Run Code Online (Sandbox Code Playgroud)

这导致:

错误:“事物”类型的参数不可分配给“{[键:字符串]:字符串|”类型的参数 数字; }'。属性“thing1”与索引签名不兼容。输入'字符串| undefined' 不能分配给类型 'string | 数字'。类型 'undefined' 不能分配给类型 'string | 数字'。

当然,如果参数的类型被强制设为可选,它会起作用:

function processObject (obj: { [key: string]: string | number | undefined }): string {
    return "test"
}
Run Code Online (Sandbox Code Playgroud)

不过,我不明白为什么这是必要的。根据TS PR #7029,可索引类型应该与具有相同隐式索引签名的其他类型兼容。

这些属性是否可选无关紧要——该属性不应该出现在对象上——对吗?为什么我必须指定undefined?有没有更好的方法来做到这一点?

jca*_*alz 2

根据您的定义,考虑以下行将在没有警告的情况下进行编译Thing

const A: Thing = { thing2: undefined };
Run Code Online (Sandbox Code Playgroud)

这使得调用时的错误在processObject() 技术上是正确的;当您只期望 a或 a时,您可能最终会undefined从对象中读取一个值。stringnumber


解释:

TypeScript 并不总是区分值(如对象属性或函数参数)缺失的情况和值存在undefined. 这种混乱是从 JavaScript 继承的,其中的差异可能很微妙:如果我有一个名为obj、 和obj.prop === undefinedis的 JavaScript 对象true,我无法"prop" in obj从中判断是 true 还是 false。

GitHub 中存在一个长期悬而未决的问题,microsoft/TypeScript#13195,要求在区分undefinedTypeScript 中的“缺失”方面保持一定的一致性。可选属性被视为“可能缺失”和“可能undefined”,而相反,定义| undefined中的必需属性不允许缺失。现在没有办法说“我想要一个可能会丢失的属性,但如果它存在,它就不应该存在undefined”。

此外,索引签名还遭受相反方向的双重思考:实际上,您可以undefined从中获取属性值(因为任何给定的属性可能会丢失),但编译器不会承认这一点(并且表现得好像每个可能的属性都存在并定义) )。请参阅microsoft/TypeScript#13778,了解自动包含undefined在索引签名属性域中的建议以及有关它的无数评论。有一个即将推出的功能标志--noUncheckedIndexedAccess(请参阅microsoft/TypeScript#39560)旨在解决此问题,但即使您打开它,您的代码仍然会遇到相同的问题,因此它并不能在所有情况下完全修复它。

undefined可选属性中处理缺失值和值的方式与索引签名中处理缺失值和值的方式之间的不匹配是导致问题的原因。


在我看来,处理这个问题的方法只是将其添加undefined到您的索引签名中,并明确承认可能存在可选属性,但是undefined. 这并不理想,但至少更接近一致。


Playground 代码链接