【问题标题】:How to forward (well-known) symbols in a type wrapper (mapped type)?如何在类型包装器(映射类型)中转发(众所周知的)符号?
【发布时间】:2019-07-19 03:32:56
【问题描述】:

按照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&lt;T&gt; 的行为与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&lt;T&gt; 的类型定义来支持Date 这样的类型?


背景: 这个简化示例背后的实际问题出现在Solid (framework),它使用a recursive version of such a wrapper 进行核心状态处理。在这种情况下,问题非常严重,因为 TypeScript 不接受 Wrapped&lt;MyState&gt;MyState,只要在嵌套状态的某个地方存在像 Date 这样的“不可包装”类型,导致 @ 987654337@ 无处不在。这个问题的目标是让 Solid 在 TypeScript 中更有用。


我尝试了什么:我已经对此进行了几个星期的修改,但没有成功。 2.9 release note 提到映射类型 { [P in K]: XXX } 现在也支持符号,但我不知道它在语法上的外观。

export type Wrapped<T> = {
  [P in keyof T]: T[P];
} & {
  [symbol in keyof T]?: T[symbol];  // doesn't work
} & { _state: number }

“工作”是将包装器编写为

export type Wrapped<T> = {
  [P in keyof T]: T[P];
} & { _state: number } & {
  [Symbol.toPrimitive](hint: "string"): string;
  [Symbol.toPrimitive](hint: "default"): string;
  [Symbol.toPrimitive](hint: "number"): number;
}

但显然我不想无条件地将这些符号添加到所有包装类型。基本上,我正在寻找表达“如果符号在 T 中,则将其添加到包装类型”的语法。如果这在一般情况下是不可能的,是否至少可以为一组众所周知的符号进行硬编码?

我在 GitHub 上发现了一些可能相关的问题,但作为一个 TypeScript 初学者,我无法理解它们:

【问题讨论】:

    标签: typescript


    【解决方案1】:

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

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

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

    【讨论】:

    • 感谢您解释背景并指出 GitHub 活动——这将是我的后续问题。巧合的是,“修复可用”是 3 天前发布的,时机很好。
    【解决方案2】:

    您可以使用conditional types

    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
    

    更新 @jcalz 提出了一个简短的解决方案

    type AddSymbolToPrimitive<T> = T extends 
      {[Symbol.toPrimitive]: infer V;} ? 
      {[Symbol.toPrimitive]: V;} : {};  
    
    export type Wrapped<T> = {
      [P in keyof T]: T[P];
    } & { _state: number } & AddSymbolToPrimitive<T>
    

    【讨论】:

    • 我认为您可以将所有这些都替换为 type AddSymbolToPrimitive&lt;T&gt; = T extends { [Symbol.toPrimitive]: infer V; } ? { [Symbol.toPrimitive]: V } : {};
    • 还有另一个挑战,包装函数:let a = () =&gt; { return "" }。是否也可以通用继承调用签名?添加条件类型 type Callable&lt;T&gt; = T extends {(): infer V;} ? {(): V;} : {}; 使其适用于这个特定的函数签名,但会失败,例如let a = (x: number) =&gt; { return "" }.
    • 看起来this answer 可以解决这个问题。我现在正在使用:type AddCallable&lt;T&gt; = T extends {(...x: any[]): infer V;} ? {(...x: Parameters&lt;T&gt;): V;} : {};
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多