如何在类型包装器(映射类型)中转发(众所周知的)符号?

时间:2019-07-18 18:24:12

标签: typescript

根据mapped types上的文档,可以在TypeScript中使用这样的包装器:

export type Wrapped<T> = {
  [P in keyof T]: T[P];
} & { _state: number }

function wrap<T extends object>(x: T): Wrapped<T> {
  let xWrapped = x as Wrapped<T>
  xWrapped._state = 0;
  return xWrapped;
}

通常,这似乎很好用,Wrapped<T>的行为与T完全一样。

但是,我注意到在某些情况下类型系统不满意。例如:

let a = new Date()
let b = wrap(a)

function f(d: Date) {}

f(a) // works
f(b) // error: Property '[Symbol.toPrimitive]' is missing in type 'Wrapped<Date>' but required in type 'Date'

如何编写Wrapped<T>的类型定义来支持Date之类的类型?


背景:此简化示例背后的实际问题来自Solid (framework),该问题使用a recursive version of such a wrapper进行核心状态处理。在这种情况下,问题非常严重,因为TypeScript不接受Wrapped<MyState>的{​​{1}},只要嵌套状态下的某个地方出现诸如{{1 }},导致MyState的转换到处都是。这个问题的目的是使TypeScript更加实用。


我尝试过的事情:我已经对此进行了几周的修改,但没有成功。 2.9 release note提到映射类型Date现在也支持符号,但是我不知道它在语法上的外观。

... as any as X

“工作原理”是将包装器写为

{ [P in K]: XXX }

但是显然我不想无条件地将这些符号添加到所有包装的类型中。基本上,我正在寻找表示“如果符号在T中,请将其添加到包装的类型中”的语法。如果通常无法做到这一点,那么是否至少可以对一组知名符号进行硬编码?

我在GitHub上发现了一些可能相关的问题,但是作为TypeScript的初学者,我对它们没有多大意义:

2 个答案:

答案 0 :(得分:2)

您可以使用conditional types

dict

更新 @jcalz

提出了一个简短的解决方案
type AddSymbolToPrimitiveDefault<T> = T extends 
  {[Symbol.toPrimitive](hint: "default"): string;} ? 
  {[Symbol.toPrimitive](hint: "default"): string;} : {};

type AddSymbolToPrimitiveString<T> = T extends 
  {[Symbol.toPrimitive](hint: "string"): string;} ? 
  {[Symbol.toPrimitive](hint: "string"): string;} : {};

type AddSymbolToPrimitiveNumber<T> = T extends 
  {[Symbol.toPrimitive](hint: "number"): number;} ? 
  {[Symbol.toPrimitive](hint: "number"): number;} : {};


type AddSymbolToPrimitiveTString<T> = T extends 
  {[Symbol.toPrimitive](hint: string): string | number;} ? 
  {[Symbol.toPrimitive](hint: string): string | number;} : {};

export type Wrapped<T> = {
  [P in keyof T]: T[P];
} & { _state: number } & AddSymbolToPrimitiveDefault<T>
 & AddSymbolToPrimitiveString<T> & AddSymbolToPrimitiveNumber<T> & AddSymbolToPrimitiveTString<T>

function wrap<T extends object>(x: T): Wrapped<T> {
  let xWrapped = x as Wrapped<T>
  xWrapped._state = 0;
  return xWrapped;
}

let a = new Date()
let b = wrap(a);

function f(d: Date) {}

f(a); // works
f(b); // works too

答案 1 :(得分:1)

问题是well-known symbols are not included as part of keyof,因此您无法映射它们。该问题被混淆地标记为“已解决”,但是我认为这是一个错误或误导;其也标记为“可用的修复程序”,这是正确的:this pull request应该解决合并后的问题。这里的活动看起来足够新(2019年6月),因此在不远的将来有一定的机会用该语言查看该活动,但是您可能想继续研究这些问题并给它们?或描述您的用例,如果您认为它们特别引人注目。

对于您来说,我没有一个很好的解决方法,因为目前很难检测到这些知名的符号键。如果您进行任何特殊包装,它可能必须是将这些符号作为键的类型,而不是键本身。哦,好吧。编辑:@NailAchmedzhanov的answer对我来说似乎是一个可行的解决方法。

无论如何,希望能有所帮助;祝你好运!