将对象属性推断为文字

问题描述 投票:0回答:1

我试图将对象的所有属性推断为文字。

我很清楚我可以使用

const
断言。

但是,我不希望结果类型具有

readonly
属性。

问题是这样的:

type Options = {
  min?: number;
};

class E1<O extends Options> {
  constructor(options: O) {}
}

const e1 = new E1({ min: 1 });

e1
的类型是:

E1<{
    min: number; // Oh no! This should be `1`
}>

现在,我知道我可以使用

const
类型参数,如下所述:TypeScript 5.0 Docs

让我们尝试一下:

class E2<const O extends Options> {
  constructor(options: O) {}
}

const e2 = new E2({ min: 1 });

e2
的类型是:

E2<{
    readonly min: 1; // Now it's `1`. Great! But it's `readonly`?
}>

我想要的结果是这样的:

typeof

eN
:(
eN
=“示例编号 N”)

EN<{
   min: 1; 
}>

现在我尝试了几种解决方案,但都不起作用:

class E3<const C extends Options> {
  constructor(c: { -readonly [K in keyof C]: C[K] }) {}
}

const e3 = new E3({ min: 1 });

类型

e3

E3<{
    readonly min: 1;
}>

或者这个:

type NoRead<T> = {
  -readonly [K in keyof T]: T[K];
};

class E4<const C extends NoRead<Options>> {
  constructor(c: C) {}
}

const e4 = new E4({ min: 1 });

类型

e4

E4<{
    readonly min: 1;
}>

对此有一个明显的解决方案:

class E5<C extends Options> {
  constructor(c: C) {}
}

const e5 = new E5({ min: 1 as const });

类型

e5

E5<{
    min: 1;
}>

但是,随着配置对象大小的增长,const 断言变得不合理。

现在,我已经可以看到这个问题:“你为什么想要那个?”

答案是,它主要用于 IntelliSense 目的,以减少此类更复杂结构中的视觉负载。

我希望这是足够的理由。

摘要:“我想将泛型的值推断为文字,同时避免使用

readonly
修饰符。”

这里是 Playground 链接:TS Playground

typescript
1个回答
0
投票

您可以通过使用“反向映射类型”来实现此目的(请参阅 microsoft/TypeScript#53018 上的评论),也称为“从映射类型推断”(如已弃用的 TS 手册中记录的 ,但由于某种原因而不是这样)在当前的版本中,尽管这一点没有任何改变)。

也就是说,如果您有 function foo<T>(ft: F<T>): void 形式的

generic
调用或构造签名,那么 TypeScript 有时可以从
映射类型
T 的值推断出类型参数
F<T>
。 只有当映射类型
F<T>
T 中的 同态
“同态映射类型”是什么意思?
)时,这才有可能。这是一个“反向”映射类型,因为如果您调用
foo(fa)
,TypeScript 必须反向运行映射类型,从输出
T
获取输入
F<T>

对于您的示例代码,它看起来像这样:

class E<const C extends Options> {
  constructor(c: Readonly<C>) { }
}

这里我们仍然使用

const
类型参数
C
来鼓励文字类型的推断。但我们说构造函数参数
c
的类型是
Readonly<C>
,使用
Readonly
实用程序类型
(实现为同态映射类型
type Readonly<T> = { readonly [P in keyof T]: T[P]; }
)。

它的意思是这样的:当你调用

new E(c)
时,TypeScript 会将
c
的类型视为
Readonly<C>
。为了从
C
的类型推断
c
,需要反转
Readonly<>
的操作。这意味着
C
在概念上是 typeof c
non
只读版本:

const e = new E({ min: 1, str: "abc" });
/* const e: E<{ min: 1; str: "abc"; }> */

显然这有效。

const
类型参数的行为就像您调用
new E({min: 1, str: "abc"} as const
一样,这会推断
c
属于
{readonly min: 1, readonly str: "abc"}
类型,并且由于它是
Readonly<C>
,TypeScript 将
C
{min: 1, str: "abc"}
进行匹配。实际上,这对我来说有点令人惊讶,因为可以说
C
仍然是
{readonly min: 1, readonly str: "abc"}
是正确的;因为无论哪种方式
Readonly<C>
都是相同的。但幸运的是,TypeScript 决定实际应用
-readonly
映射修饰符


在反向映射类型不能神奇地工作的情况下,您需要自己做。也就是说,如果

new <C extends Options>(c: Readonly<C>) => E<C>
没有这样做,那么
new <C extends Options>(c: C) => E<Mutable<C>>
应该这样做,其中
type Mutable<T> = { -readonly [K in keyof T]: T[K] }>
。不幸的是,TypeScript 永远不会为
class
声明推断出这样的事情,这意味着您需要自己手动编写它并为其分配一个构造函数:

class _E<C extends Options> {
  constructor(c: C) { }
}
type E<C extends Options> = _E<C>;
const E: new <const C extends Options>(
  e: C) => E<{ -readonly [K in keyof C]: C[K] }> = _E;

这给出了相同的结果:

const e = new E({ min: 1, str: "abc" });
/* const e: E<{ min: 1; str: "abc"; }> */

但这更复杂,因此反向映射类型在工作时更可取。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.