通用对象属性上的类型防护在打字稿中不起作用

时间:2020-04-19 14:38:17

标签: typescript generics typeguards

例如,我有一个非常简单的函数,它为某些命名对象属性设置“ true”(布尔值):

function setTrue<T, K extends keyof T>(host: T, property: K) {
    if (typeof host[property] === 'boolean')
        host[property] = true; // <-- TS2322: Type 'true' is not assignable to type 'T[K]'.
}

无法找出问题所在?在这种情况下,类型防护typeof host[property] === 'boolean'似乎不起作用。谁知道在此示例中如何实现正确的静态类型检查

我发现的唯一解决方法是如果T extends any,但是在这种情况下根本不需要类型保护:

function setTrue<T extends any, K extends keyof T>(host: T, property: K) {
    host[property] = true; // <-- Always Ok
}

1 个答案:

答案 0 :(得分:0)

我认为有多种因素导致这种情况。

一个问题是,当要测试的属性键是变量时,编译器不会缩小类型保护范围;有关更多信息,请参见microsoft/TypeScript#10530

另一个因素是,不能安全地完成对密钥类型未知的单例属性的读取和后续写入。参见microsoft/TypeScript#32693。编译器没有意识到property是这两行中的完全相同的键。它所了解的就是所访问的属性具有相同的 type ,除非已经知道类型是单例的,否则这还不够严格。

最后,面对依赖于未指定的泛型类型参数(例如TK)的值,编译器在控制流分析方面并不擅长。规范的问题是microsoft/TypeScript#13995。即使您测试propertykeyof T的特定子类型,编译器也不会缩小K参数的范围。充其量只能将property的类型缩小到某个交叉点K & ...,这可能会或可能不会有帮助。


在显示变通办法之前,请先在此处提出以下警告:从技术上讲,这并不安全。如果您的主机具有一个类型为布尔文字false的属性,例如:

interface Foo {
  bar: false
}
declare const foo: Foo;

然后您将遇到问题:

setTrue(foo, "bar");
foo.bar; // false or true?

您对setTrue()的实现只是检查foo.bar是否为boolean类型。但是现在,我们通过将 type false的属性设置为值true来欺骗编译器。因此,foo.bar现在在运行时为true,而在您的IDE中为false

我将忽略这种皱纹,但它仍然存在,因此请小心。


因此,解决方法。到目前为止,最简单的方法就是使用type assertion并继续:

function setTrue<T, K extends keyof T>(host: T, property: K) {
  if (typeof host[property] === 'boolean')
    (host as any as Record<K, boolean>)[property] = true; 
}

这里我们断言,如果host[property]的类型为boolean,那么我们会将host视为Record<K, boolean>:一个属性值为{{1 }},类型为boolean

最简单的方法是将其重构为一些较小的部分,使编译器可以自行验证或可以将其制成user-defined type guard,以使编译器按照我们希望的方式进行类型分析:

K

仅允许在已知function setTrueKnownBoolean<K extends PropertyKey>(host: Record<K, boolean>, property: K) { host[property] = true; } function isBooleanProperty<K extends keyof T, T>( host: T, property: K): host is T & Record<K, boolean> { return typeof host[property] === "boolean"; } function setTrue2<K extends keyof T, T>(host: T, property: K) { if (isBooleanProperty(host, property)) { setTrueKnownBoolean(host, property); } } 键具有setTrueKnownBoolean属性的host个对象上调用函数boolean,因此实现需要里面没有类型检查。函数property充当类型保护,将isBooleanProperty的类型从host缩小到T。现在T & Record<K, boolean>可以通过组合其他两个函数来实现。


好的,希望能为您提供一些指导。祝你好运!

Playground link to code