【问题标题】:TypeScript - class generic type is too narrow if extends something else, is not if does not extend anythingTypeScript - 如果扩展其他东西,类泛型类型太窄,如果不扩展任何东西,则不是
【发布时间】:2021-07-08 05:38:03
【问题描述】:

我有 Item<T>StrictItem<T extends Something> 这样的课程。如果我将前者初始化为Item('a'),它将被视为Item<string>,而StrictItem<'a'> 将被视为StrictItem<'a'>

  1. 为什么对泛型施加约束会导致这些差异?好像没有关系。
  2. 有没有办法改变StrictItem 的定义,所以StrictItem('a') 将是StrictItem<string>,同时仍然保留对泛型的约束?

Playground

interface PlainObject<T> { [index: string ]: T}

type ItemValue =
  boolean |
  number |
  string |
  ItemValue[] |
  { [index: string ]: ItemValue } |
  null;

class Item<T> {
    private value: T;
    constructor(value: T) {
        this.value = value;
    }
    public set(value: T) {
        this.value = value;
    }
}

// This one puts restrictions on the generic
class StrictItem<T extends ItemValue> extends Item<T> {
}

// 1. Why does the error seen in item2 does not occur here?
let item1 = new Item('a');
item1.set('b');
item1.set(0); // error

// 1. Can StrictItem definition be modified to implictly infer a more
// general type ('string') in this case (without using any type for T)?
// Or is explicitly defining the type of passed value the only way (see item3)?
let item2 = new StrictItem('a');
item2.set('b'); // error

let item3 = new StrictItem('a' as string);
item3.set('b');

【问题讨论】:

  • 你可以使用new StrictItem&lt;string&gt;('a')

标签: javascript typescript typescript-typings typescript-generics


【解决方案1】:

为什么对泛型施加约束会导致这些差异?好像没有关系。

我相信当您执行&lt;T&gt; 时,您会告诉编译器,“只需使用通常会推断出的任何内容。在let foo = 'bar' 中,foo 被推断为string。没有额外的符号,这是有道理的作为默认设置。

但是,&lt;T extends ItemValue&gt; 说的有些不同。这告诉编译器“T 是扩展 ItemValue 的类型,这意味着它可能是更具体的子类型。”在这种情况下,它将推断出最具体的类型。

所以如果你约束一个泛型,你应该期望它是一个超级特定的类型。通常,这就是您想要的。


有没有办法改变 StrictItem 的定义,所以 StrictItem('a') 将是 StrictItem&lt;string&gt;,同时仍然保留对泛型的约束?

我不知道。但这通常不是问题。很难从您设计的示例中建议采取行动,但通常您实际上并没有将字符串文字硬编码为这样的类型。相反,数据可能来自其他数据源、函数或变量。

在这种情况下,您只需将类型为 string 的变量传递给它,它就会像您期望的那样工作。

let item4Data = getSomeString() // returns string
let item4 = new StrictItem(item4Data) // StrictItem<string>

如果你真的只是想传入一个字面量,你总是可以在使用类型时显式地传入一个类型参数。这是类型安全的,因为会根据您提供的类型检查参数的类型。

let item5 = new StrictItem<string>('a')
item5.set('b') // works

let item6 = new StrictItem<string>(123) // type error

【讨论】:

  • 使用StrictItem&lt;string&gt;('a') 似乎是最优雅的解决方案。谢谢。
【解决方案2】:

这里有一个解决方案:

interface PlainObject<T> { [index: string]: T }

type ItemValue =
    | boolean
    | number
    | string
    | ItemValue[]
    | { [index: string]: ItemValue }
    | null;

class Item<T> {
    private value: T;
    constructor(value: T) {
        this.value = value;
    }
    public set(value: T) {
        this.value = value;
    }
}

type Infer<T> = T extends infer R ? R : never

// This one puts restrictions on the generic
class StrictItem<T extends ItemValue> extends Item<Infer<T>> { }

let item2 = new StrictItem('a');
item2.set('b'); // ok

您可以使用infer 来获取更通用的类型并将其包装

更新 作为替代解决方案,U 可以添加第二个具有默认值的泛型参数:

interface PlainObject<T> { [index: string]: T }

type ItemValue =
    | boolean
    | number
    | string
    | ItemValue[]
    | { [index: string]: ItemValue }
    | null;

class Item<T> {
    private value: T;
    constructor(value: T) {
        this.value = value;
    }
    public set(value: T) {
        this.value = value;
    }
}

type Infer<T> = T extends infer R ? R & ItemValue : never

// This one puts restrictions on the generic
class StrictItem<T, R = Infer<T>> extends Item<R> { }

let item2 = new StrictItem('a');
item2.set('2'); // ok

【讨论】:

  • 那行不通。似乎泛型类型丢失了,item2StrictItem&lt;ItemValue&gt; 而不是StrictItem&lt;string&gt;。例如,item2.set(1); 也不会给出错误。我想我会很好地初始化像new StrictItem&lt;string&gt;('a') 这样的类,就像你之前在评论中建议的那样。当我发布问题时,我不知道这种语法。
  • 谢谢。我很感激。尽管如此,我还是决定使用new StrictItem&lt;string&gt;('a')
  • @MaciejKrawczyk spoko)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-25
相关资源
最近更新 更多