【问题标题】:Typescript: [Compile Time] Writable (- readable) array/object with "as const" type narrowing打字稿:[编译时间]具有“as const”类型缩小的可写(-可读)数组/对象
【发布时间】:2020-11-21 14:05:22
【问题描述】:

使用const assertion,可以很好地将对象/数组字面量的类型缩小到其元素。

例如

const arr = [
  [5, "hello"],
  [5, "bye"],
] as const;

type T = typeof arr; // type T = readonly [readonly [5, "hello"], readonly [5, "bye"]]

(如果没有as constT 将是type T = (string | number)[][],它非常宽,有时不需要。)

现在,问题是由于as const,数组也变成了readonly,而我只是将它的类型缩小了。所以,它不能传递给下面的函数。

function fiveLover(pairs: [5, string][]): void {
  pairs.forEach((p) => console.log(p[1]));
}

fiveLover(arr); // Error

错误是:

'readonly [readonly [5, "hello"], readonly [5, "bye"]]' 类型的参数不能分配给'[5, string][]' 类型的参数。 'readonly [readonly [5, "hello"], readonly [5, "bye"]]' 类型为'readonly',不能赋值给可变类型'[5, string][]'.(2345)

问题

如何缩小类型,而不会获得不需要的readonly 属性? (最好在对象/数组创建时。)

【问题讨论】:

    标签: typescript constants readonly mutable narrowing


    【解决方案1】:

    Typescript as 运算符对我来说看起来有点老套。我试图尽可能地避免它。 请尝试下一个示例:

    const arr = [
      [5, "hello"],
      [5, "bye"],
    ] as const;
    
    function fiveLover<T extends ReadonlyArray<readonly [5, string]>>(pairs: T): void {
      pairs.forEach((p) => console.log(p[1]));
    }
    
    fiveLover(arr); // No Error
    

    顺便说一句,我一直在尝试对不可变的值进行操作。如果您没有改变您的值,则没有必要删除 readonly 标志

    更新

    可以在没有const assertion 的情况下进行推断,但您需要提供文字对象而不是引用。没有别的办法

    
    function fiveLover<
      Fst extends number,
      Scd extends string,
      Tuple extends [Fst, Scd],
      Tuples extends Tuple[]
    >(pairs: [...Tuples]): void {
      pairs.forEach((p) => console.log(p[1]));
    }
    
    fiveLover([
      [5, "hello"],
      [5, "bye"],
    ]); // ok
    

    Playground

    【讨论】:

    • 感谢您的回答。您的解决方案没有错,但它不适用于函数存在于外部库中并且我们无法修改它的情况。库通常不会沉迷于每个函数参数的嵌套 readonly&lt;readonly&lt;X&gt;&gt; 类型。这就是为什么我一直在寻找一个专注于对象本身的解决方案。再次感谢。
    【解决方案2】:

    为了摆脱readonly属性并使对象或数组可变,我们需要以下实用函数:

    type DeepWritable<T> = { -readonly [P in keyof T]: DeepWritable<T[P]> };
    

    (来源:https://stackoverflow.com/a/43001581

    现在我们可以做以下两件事之一:

    解决方案1)使其在消费时可变

    即:

    fiveLover(arr as DeepWritable<typeof arr>);
    

    这很棒,并且可以保持对象完好无损。但是,如果我们在多个地方使用 arr 数组来实现类似的可变用法,我们需要每次都这样做。

    解决方案 2) 在创建时使其可变

    这是我在实际项目中所需要的,因为我在很多地方都使用了 arr 对象,而且它是只读的会导致麻烦。

    解决方案是定义一个实用函数,它在运行时只是一个标识函数,但在编译时是DeepWritable,如下所示:

    function writableIdentity<T>(x: T): DeepWritable<T> {
        return x as DeepWritable<T>;
    }
    
    const arr2 = writableIdentity([
      [5, "hello"],
      [5, "bye"],
    ] as const);
    
    fiveLover(arr2); // Works fine!
    

    TS Playground link.

    这样,对象只被操作/转换一次,它可以在任何地方用作普通的、类型缩小的、非只读变量!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-18
      • 2021-04-20
      • 2019-03-22
      • 2020-12-05
      • 2021-10-12
      • 1970-01-01
      相关资源
      最近更新 更多