代码之家  ›  专栏  ›  技术社区  ›  Exifers

Record与泛型键扩展模板文字的交集不会推断索引访问的结果

  •  1
  • Exifers  · 技术社区  · 9 月前

    原则上 (T & Record<K, U>)[K] 应该评估 U 但在以下情况下,它似乎不起作用 K 是通用的,扩展了模板文字。

    function foo3<
        K extends `a${string}`,
    >(k: K) {
        const a = {} as {b: 1} & Record<K, string>
    
        const r = a[k]
        r.toLowerCase() // Property 'toLowerCase' does not exist on type '({ a: 1; } & Record<K, string>)[K]'.(2339)
    }
    

    它适用于K扩展“简单”类型的情况,如 string 或字符串文字。

    Playground

    有什么方法可以使这项工作正常进行并保持类型安全?

    2 回复  |  直到 9 月前
        1
  •  2
  •   Brother58697    9 月前

    如果我们避免使用泛型类型作为索引,我们可以解决这个问题。相反,我们可以用与参数相同的类型对其进行索引,这样它就会被正确地索引。

    type PrefixedA = `a${string}`;
    
    function foo<
        K extends PrefixedA,
    >(k: K) {
        const a = {} as { b: 1 } & Record<PrefixedA, string>
        const r = a[k]
        r.toLowerCase() // No error
    }
    

    Playground

        2
  •  1
  •   jcalz    9 月前

    如果TypeScript无法理解这一点 (T & Record<K, U>)[K] 等同于(或其子类型) U ,我通常加宽 T & Record<K, U> Record<K, U> 通过重新分配,因为TypeScript似乎确实建模得更简单 Record<K, U>[K] 可分配给 U 例如:

    function foo<K extends `a${string}`>(k: K) {
        const a = {} as { b: 1 } & Record<K, string>
        const _a: Record<K, string> = a; // widen a
        const r = _a[k];
        r.toLowerCase();
    }
    

    在这里 _a 只是 a 但它的类型已经从 {b: 1} & Record<K, string> Record<K, string> 一旦你有了,那么 _a[k] 可以分配给 string .

    请注意,TS并不总是 sound 而且肯定是不完整的,所以像这样的泛型类型操作最终可能会在不应该的情况下奏效或失败 T&记录<K、 U> 记录<K、 U> 通常 安全,但TS的类型系统有一些漏洞,在边缘情况下会破坏等效性。因此,如果所有其他方法都失败了,并且你已经说服自己所做的事情是安全的,那么使用 type assertion 喜欢 const r = a[k] as string .

    Playground link to code