TypeScript中针对对象字面量的动态泛型类型推断

15
在TypeScript中,我可以这样声明一个泛型函数:
const fn: <T>(arg: T)=>Partial<T>

在这种情况下,TypeScript有时可以根据我传递给函数的实际参数推断出函数的类型参数。是否有一种类似的方法来定义一个泛型对象字面量,其类型参数可以基于其内容动态推断呢?例如:

interface XYZ { 
  obj: <T>{ arr: T[], dict: Partial<T> }
}

我知道我可以像这样使整个界面通用:
interface XYZ<T> {
  arr: T[],
  dict: Partial<T>
}

但我希望避免这种情况,因为这样每次使用接口时都需要提前声明通用类型。例如:

const x: XYZ

这不会起作用。如果我想要使声明通用,我被迫写成:

const x: XYZ<any>

但这并不允许TypeScript根据x的实际内容动态推断特定的泛型类型。

2个回答

14
啊,您想要如Microsoft/TypeScript#17574所讨论的通用值。正如您所指出的那样,除了通用函数,它们在语言中不存在。如果您认为有帮助,您可以提交问题报告或讨论您的用例。
鉴于通用接口。
interface XYZ<T> {
  arr: T[],
  dict: Partial<T>
}

我会使用以下方法来解决问题:创建一个通用函数,以验证某个值是否为XYZ<T>,其中T是任意类型,并允许类型推断在必要时实际推断T。永远不要尝试声明XYZ类型的变量。像这样:
const asXYZ = <T>(xyz: XYZ<T>) => xyz;

const x = asXYZ({
  arr: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
  dict: { a: 1300 }
}); // becomes XYZ<{a: number, b: number}>

以上在实践中通常对我有效。优点是它是“自然”的TypeScript。缺点是它不能正确地表示“我不关心类型T是什么”的含义。


如果你真的想要,你可以定义一个存在类型。 TypeScript 并不原生支持这些类型,但有一种表达方式:

interface SomeXYZ {
  <R>(processXYZ: <T>(x: XYZ<T>) => R): R
}
const asSomeXYZ = <T>(xyz: XYZ<T>): SomeXYZ => 
  <R>(processXYZ: <T>(x: XYZ<T>) => R) => processXYZ(xyz);

SomeXYZ类型是一个具体类型,不再关心T,但仍然持有对于某个T的XYZ<T>的引用。您可以使用asSomeXYZ从对象创建一个SomeXYZ实例:

const someXYZ: SomeXYZ = asSomeXYZ({
  arr: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
  dict: { a: 1300 }
}); // SomeXYZ

您可以通过传递一个处理所持有引用的函数来使用它。该函数必须准备好处理任何TXYZ<T>,因为您不知道SomeXYZ持有哪种类型的T

// use one
const xyzArrLength = someXYZ((xyz => xyz.arr.length))
xyzArrLength 是一个数字,因为函数 xyz => xyz.arr.length 无论 T 是什么都会返回一个数字。
在 TypeScript 中,存在类型有些棘手,因为控制权的反转很多。这是主要的缺点,也是我通常采用第一种不太完美但更易于思考的解决方法的原因。
希望这有所帮助。祝你好运!
编辑:重新阅读您的问题让我想到您实际上正在寻求我列出的“解决方法”的答案。那么...使用它吧?干杯。

有趣的评论和解决方法;我会尝试这些。我还在TypeScript存储库(github.com/Microsoft/TypeScript/issues/24375)上提交了一个问题,但考虑到您指向了其他问题,我可能会将我的问题与那个问题合并。 - prmph
@jcalz 所以我们可以这样定义一些类型:type Foo = <T>( param: T ) => T?这意味着类型Foo是一个具有参数类型为T的函数,并且函数返回类型也是T吗?与type Foo<T> = ( param:T ) => T相比,有什么区别吗? - Archsx
@Archsx 请查看 https://dev59.com/qcTra4cB1Zd3GeqP6mrD#72279919 - jcalz
抱歉,我不会在四年前回答的问题评论区解决问题。如果您有新的问题,最好发布一个新的问题,如果我有时间,我可能会看一下。祝你好运! - jcalz
好吧,我撒谎了,因为我看了。存在泛型的整个重点在于它们对消费者是不透明的。如果您需要访问传递的类型参数,则不应使用存在类型。至于您想要使用什么,那是我无法在旧问题的评论部分中承诺过多关注的事情。再次祝你好运! - jcalz

1
interface MyGenericObjectLiteral<T> {
  arr: T[],
  dict: Partial<T>
}

interface XYZ { 
    obj: MyGenericObjectLiteral<any>
}

这里的XYZ接口不会是通用的,只是子对象MyGenericObjectLiteral。实际上它就是你想要的,只是语法有点不同。

我知道我可以这样做,但我想避免让整个接口变得通用。 - prmph
1
@Isaev 但是指定“any”作为泛型类型意味着你失去了动态类型推断。 - prmph
1
@prmph 是的。在 TypeScript 中,没有所谓的通用对象字面量概念。接口旨在描述对象形状,并且它们可以是通用的,但您必须指定 T。 - Kit Isaev
1
@Isaev:但如果它适用于函数,那么就没有技术上的理由不能适用于对象。TypeScript现在可能不支持它,但它_可以_支持它。 - prmph
@prmph同意,这更像是TypeScript设计的陷阱,而不是基于逻辑的事情。在我使用TypeScript的经验中,最糟糕的印象之一就是不断地尝试猜测什么应该明确指定,什么可以自动推断。 - Kit Isaev
让我们在聊天中继续这个讨论 - Kit Isaev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接