【问题标题】:Type guard on generic object property doesn't work in typescript通用对象属性的类型保护在打字稿中不起作用
【发布时间】:2020-08-01 23:46:39
【问题描述】:

例如,我有一个非常简单的函数,它将“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
}

【问题讨论】:

    标签: typescript generics typeguards


    【解决方案1】:

    我认为造成这种情况的原因有很多。

    一个问题是当被测试的属性键是一个变量时,编译器不会进行类型保护缩小;请参阅microsoft/TypeScript#10530 了解更多信息。

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

    最后,面对依赖于未指定的泛型类型参数(如TK)的值,编译器只是不太擅长控制流分析。规范的问题是microsoft/TypeScript#13995。即使您测试propertykeyof T 的特定子类型,编译器也不会缩小K 参数,请参阅microsoft/TypeScript#24085。充其量它会将property 的类型缩小到某个路口K &amp; ...,这可能有用也可能没有帮助。


    在展示变通方法之前,需要在此处提出一个警告:这在技术上并不安全。如果您的主机有一个类型已知为布尔文字 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&lt;K, boolean&gt;:在K 类型的键上具有boolean 类型的属性值的对象。

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

    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 对象上调用,这些对象在property 键处已知具有boolean 属性,因此实现不需要内部类型检查。函数isBooleanProperty 充当类型保护,将host 的类型从T 缩小到T &amp; Record&lt;K, boolean&gt;。现在setTrue2()可以通过组合这两个函数来实现。


    Playground link to code

    【讨论】:

      猜你喜欢
      • 2018-06-08
      • 1970-01-01
      • 2021-03-11
      • 2017-09-06
      • 2017-03-31
      • 2021-03-25
      • 2021-12-06
      • 2019-03-16
      • 1970-01-01
      相关资源
      最近更新 更多