打字稿:[编译时间] 可写(-可读)数组/对象,具有“as const”类型缩小

Typescript: [Compile Time] Writable (- readable) array/object with "as const" type narrowing

提问人:Aidin 提问时间:11/21/2020 更新时间:9/18/2021 访问量:871

问:

使用 const 断言,可以很好地将对象/数组文本的类型缩小到其元素。

例如:

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

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

(如果没有 ,将是 ,这是非常宽泛的,有时是不需要的。as constTtype T = (string | number)[][]

现在,问题是,由于数组也变得如此,而我只是让它有一个缩小的类型。因此,它不能传递给以下函数。as constreadonly

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

fiveLover(arr); // Error

错误是:

类型的参数不能分配给类型的参数。 该类型是且不能分配给可变类型。(2345)'readonly [readonly [5, "hello"], readonly [5, "bye"]]''[5, string][]''readonly [readonly [5, "hello"], readonly [5, "bye"]]''readonly''[5, string][]'

问题

如何在不获取不需要的属性的情况下缩小类型范围?(最好在对象/数组创建时。readonly

打字稿 常数 只读 可变 缩小

评论


答:

0赞 Aidin 11/21/2020 #1

为了摆脱属性并使对象或数组可变,我们需要以下实用程序函数: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 链接。

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

1赞 captain-yossarian from Ukraine 11/22/2020 #2

打字稿运算符对我来说看起来有点笨拙。我试图尽可能地避免它。 请尝试下一个示例: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

操场

评论

2赞 Aidin 11/22/2020
感谢您的回答。您的解决方案没有错,但它不适用于函数位于外部库中并且我们无法修改它的情况。对于库来说,不沉迷于每个函数参数的嵌套类型是很常见的。这就是为什么我一直在寻找一种专注于对象本身的解决方案。再次感谢。readonly<readonly<X>>