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

Posted

技术标签:

【中文标题】TypeScript 中对象字面量的动态泛型类型推断【英文标题】:Dynamic generic type inference for object literals in TypeScript 【发布时间】:2018-11-03 17:12:43 【问题描述】:

在打字稿中,我可以像这样声明一个通用函数:

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的实际内容动态推断出具体的泛型类型

【问题讨论】:

【参考方案1】:

啊,您需要Microsoft/TypeScript#17574 中讨论的通用值。正如您所注意到的,它们在语言中不存在,除非是泛型函数。如果您愿意,可以对这个问题给予 ?,或者如果您认为有帮助,可以讨论您的用例。

给定通用接口

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

我只想使用这个解决方法:创建一个通用函数来验证 some T 的值是 XYZ&lt;T&gt;,并允许类型推断在必要时实际推断 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 是什么类型”。


如果你真的想要,你可以定义一个existential type。 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&lt;T&gt; 的引用。您使用 asSomeXYZ 创建一个来自一个对象:

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

您通过传递一个处理所持有的引用的函数来使用它。该函数必须为任何T 准备好XYZ&lt;T&gt;,因为你不知道TSomeXYZ 持有什么类型。

// use one
const xyzArrLength = someXYZ((xyz => xyz.arr.length))

xyzArrLength 是一个number,因为无论T 是什么,函数xyz =&gt; xyz.arr.length 都会返回一个number

TypeScript 中的现有类型很尴尬,因为发生了很多控制反转。这是它的主要缺点,也是为什么我通常会采用我首先提出的不太完美但更容易思考的解决方法。

希望对您有所帮助。祝你好运!

编辑:重新阅读您的问题让我觉得您实际上是在询问我列为“解决方法”的答案。所以,呃……用那个?干杯。

【讨论】:

有趣的 cmets 和变通方法;我会试试这些。我还在 TypeScript 存储库 (github.com/Microsoft/TypeScript/issues/24375) 上提交了一个问题,但鉴于您指向另一个问题的指针,我可能会将我的问题与该问题合并【参考方案2】:
interface MyGenericObjectLiteral<T> 
  arr: T[],
  dict: Partial<T>


interface XYZ  
    obj: MyGenericObjectLiteral<any>

这里的接口 XYZ 不会是泛型的,只是子对象 MyGenericObjectLiteral。它实际上正是您想要的,只是它的语法有点不同。

【讨论】:

我知道我可以这样做,但我想避免让整个界面变得通用。 @Isaev 但是将“any”指定为泛型类型意味着您将失去动态类型推断 @prmph 是的。打字稿中没有通用对象文字这样的概念。接口旨在描述对象形状,它们可能是通用的,但您必须指定 T。 @Isaev:但如果它适用于函数,那意味着它不能适用于对象没有技术原因。 TypeScript 现在可能不支持它,但它可以支持它 @prmph 同意,这与其说是基于逻辑的东西,不如说是 TypeScript 设计的一个陷阱。在我使用 TypeScript 的经验中,最糟糕的印象之一就是不断尝试猜测应该明确指定什么以及可以自动推断什么。

以上是关于TypeScript 中对象字面量的动态泛型类型推断的主要内容,如果未能解决你的问题,请参考以下文章

对象字面量的 TS 接口

将泛型值推送到 Typescript 中泛型类中的泛型列表

Typescript:定义对象的类型

在 TypeScript 中动态访问 const 对象字面量

TypeScript开发实战

TypeScript教程# 4:TS中类型