我已经编写了一个类型脚本函数,该函数在给定关系类型、方向和名义nodeID的情况下,返回该关系另一端的 node 列表.例如

("A_TO_B", "from", "nodeAId") -> NodeB[]

是否有可能在不传递显式类型参数的情况下键入此函数,从而使TypeScrip正确地缩小/推断返回类型?

奇怪的是,在我当前的实现中,如果调用方显式传递前两个泛型类型参数,则推理确实有效:

export function getNeighborsByRelationship<
  T extends ExtractRelationshipsByNodeId<D>["type"],
  D extends Extract<keyof Relationship, "from" | "to"> = Extract<
    keyof Relationship,
    "from" | "to"
  >,
  R extends ExtractRelationshipByType<T> = ExtractRelationshipByType<T>,
  N extends Node = ExtractNodeById<
    R[Exclude<Extract<keyof R, "from" | "to">, D>]
  >
>(
  type: R["type"],
  dir: D,
  id: R[D]
): {
  nodes: N[];
  relationships: R[];
}

function test () {
  const id: AppleId = "A"
    
  // this does not infer the return type correctly... :(
  const res = getNeighborsByRelationship('APPLE_TO_CLOCK_AND_DOGGIE', 'from', id)

  // however, this does:
  // const res = getNeighborsByRelationship<'APPLE_TO_CLOCK_AND_DOGGIE', 'from'>('APPLE_TO_CLOCK_AND_DOGGIE', 'from', id)

  return res?.nodes[0].label
}

但是,如果不显式传递泛型类型,我就无法使推理工作.

我当前的工作是可复制的,并且包括示例 node 和关系类型,它包含在playground 链接中.提供Node和Relationship类型的 struct 只是为了方便,因为在我工作的代码库中不容易更改它们.

Playground

推荐答案

TypeScrip的类型推理算法存在一些限制,这些限制阻碍了这种方法的工作.

第一个缺失的功能在microsoft/TypeScript#20126处描述:TypeScript无法从indexed access type T[K]推断类型T. 你的type参数的类型是R["type"],但由于这个限制,TypeScript无法从中推断出R.

第二个缺失的特征在microsoft/TypeScript#7234处描述:TypeScrip不能从另一个类型参数的generic constraint中推断出类型T,例如U extends F<T>.在函数参数中的任何地方都没有提到T参数;它只是作为对R的约束出现,所以根本无法推断T.


除非这些问题得到解决,否则您将需要更改泛型类型参数.我建议的一般方法是减少到尽可能少的泛型类型参数(如果您使用default type arguments作为使用内联类型计算的快捷方式,这可能是可以接受的,但对于第一次传递,您不应该为此使用缺省值.或者计算内联类型,或者为它们编写实用程序类型),并使您的函数参数的类型尽可能与这些类型参数直接相关.就像这样:

declare function getNeighborsByRelationship<
  T extends Relationship["type"],
  D extends "from" | "to">(
    type: T,
    dir: D,
    id: ExtractRelationshipByType<T>[D]
  ): {
    nodes: (
      ExtractNodeById<ExtractRelationshipByType<T>[
        Exclude<Extract<keyof ExtractRelationshipByType<T>, "from" | "to">, D>
      ]>)[];
    relationships: ExtractRelationshipByType<T>[];
  }

这里我们只有两个泛型类型参数;Ttype的类型完全对应,DD的类型完全对应.这意味着当您调用该函数时,从前两个函数参数应该可以推断出TD.这两个类型参数应该足以计算您关心的所有其他类型,例如id的类型,以及返回值的nodesrelationships属性的元素类型.

如果这太混乱了,您可以将实用程序类型设置为

type N<R extends Relationship, D extends "from" | "to"> =
  ExtractNodeById<R[
    Exclude<Extract<keyof R, "from" | "to">, D>
  ]>;

declare function getNeighborsByRelationship<
  T extends Relationship["type"],
  D extends "from" | "to">(
    type: T,
    dir: D,
    id: ExtractRelationshipByType<T>[D]
  ): {
    nodes: N<ExtractRelationshipByType<T>, D>[];
    relationships: ExtractRelationshipByType<T>[];
  }

或者使用默认类型参数,但这超出了这里的范围.


不管怎样,让我们确保它的行为符合预期:

function test() {
  const id: AppleId = "A"
  const res = getNeighborsByRelationship('APPLE_TO_CLOCK_AND_DOGGIE', 'from', id)
  //    ^? const res: { 
  //         nodes: ExtractNodeById<"C" | "D">[]; 
  //         relationships: AppleToClockAndDoggie[]; 
  //       }
  return res?.nodes[0].label
}

const v = test()
//    ^? const v: "Clock" | "Doggie"

看上go 不错!推理正确,类型与您预期的一致.

Playground link to code

Typescript相关问答推荐

如何在不使用变量的情况下静态判断运算式的类型?

脉轮ui打字脚本强制执行图标按钮的咏叹调标签-如何关闭

您可以创建一个类型来表示打字员吗

类型安全JSON解析

TypeScrip:基于参数的条件返回类型

使用`renderToPipeableStream`时如何正确恢复服务器端Apollo缓存?

使用打字Angular 中的通用数据创建状态管理

我可以使用TypeScrip从字符串词典/记录中填充强类型的环境对象吗?

CDK从可选的Lambda角色属性承担角色/复合主体中没有非空断言

在另一个类型的函数中,编译器不会推断模板文字类型

如何使所有可选字段在打印脚本中都是必填字段,但不能多或少?

为什么&;(交集)运算符会删除不相交的属性?

在列表的指令中使用intersectionObservable隐藏按钮

在保留类型的同时进行深层键名转换

TypeScrip:强制使用动态密钥

如何正确调用创建的类型?

ANGLE NumberValueAccessor表单控件值更改订阅两次

为什么上下文类型在`fn|curred(Fn)`的联合中不起作用?

打字:注解`是字符串`而不是`布尔`?

在tabel管理区域react js上设置特定顺序