如何让打字稿正确地显示这种类型?(带有泛型的递归类型) [英] How to make TypeScript display this type properly? (recursive type with generics)

查看:0
本文介绍了如何让打字稿正确地显示这种类型?(带有泛型的递归类型)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出一个框:

type A = {readonly id: 'A'}
type B = {readonly id: 'B'}
type Value = A | B

class Box<T extends Value | {[x: string]: Value}> {
  constructor(public value: T) {}
}

我想创建一个函数merge

let a = {id: 'A'} as A
let b = {id: 'B'} as B

let merged = merge(
  new Box({position: a}),
  {velocity: new Box(a), color: new Box(b)},
  {mass: new Box(b)}
)

使得merged的类型为Box<{position: A, velocity: A, color: B, mass: B}>

这是我的想法:

// {position: Box<A>} => {position: A}
// Box<{position: A}> => {position: A}
type InferBoxValue<B extends {[x: string]: Box<Value>} | Box<{[x: string]: Value}>> =
  B extends {[x: string]: Box<Value>} ? {[K in keyof B]: B[K] extends Box<infer F> ? F : unknown} :
  B extends Box<infer F> ? F :
  unknown

// [{color: Box<A>}, Box<{mass: B}>] => [{color: A}, {mass: B}]
type InferMultipleBoxValues<A extends any[]> = {
  [I in keyof A]: A[I] extends {[x: string]: Box<Value>} | Box<{[x: string]: Value}>
    ? InferBoxValue<A[I]>
    : unknown
}

// MergeTwo<{color: A}, {mass: B}> => {color: A, mass: B}
type MergeTwo<A, B> = {
  [K in keyof (A & B)]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : unknown
}

// Merge<[{color: A}, {mass: B}, ...]> => {color: A, mass: B, ...}
type Merge<A extends readonly {[x: string]: Value}[]> = A extends [infer L, ...infer R] ?
  R extends {[x: string]: Value}[] ? MergeTwo<L, Merge<R>> : unknown : unknown

function merge<
  B extends ({[x: string]: Box<Value>} | Box<{[x: string]: Value}>)[],
  M extends Merge<InferMultipleBoxValues<B>>
>(...boxes: B): M extends {[x: string]: Value} ? Box<M> : unknown {
  return null as any // implementation is trivial
}

这在一定程度上是有效的。当我将鼠标悬停在VSCode中的merged上时,我看到以下类型:

Box<MergeTwo<{position: A}, MergeTwo<{velocity: A; color: B}, MergeTwo<{mass: B}, unknown>>>>

我可以断定它等同于我想要的类型,因为这是有效的:

let test: Box<{position: A, velocity: A, color: B, mass: B}> = null as unknown as (
  Box<MergeTwo<{position: A}, MergeTwo<{velocity: A; color: B}, MergeTwo<{mass: B}, unknown>>>>
)

但这并不完全是我想要的。我不希望Box的用户看到InferBoxValueInferMultipleBoxValuesMergeTwoMerge中的任何一个。如何调整merge的实现以使TypeScrip正确显示结果?

感谢您的关注!

使用TypeScrip v4.6.2(Playground Link)

推荐答案

实际上@ashtonSix可以跑得更远!

使用此有趣的类型:

type Expand<T> = T extends {} ? { [K in keyof T]: Expand<T[K]> } & {} : T;

它的工作方式是显式地将类型中的所有内容提取到映射类型中,这意味着它显示为";对象文字&。如果该类型是基元类型,则它不执行任何操作,并且此类型也会递归扩展。

让我们测试一下:

type Target = Box<MergeTwo<{position: A}, MergeTwo<{velocity: A; color: B}, MergeTwo<{mass: B}, unknown>>>>;

type Expansion = Expand<Target>;
编辑:哦,我只是注意到您希望它像Box<...>,考虑到它只是一个有键的对象,这是可以理解的。我想你可以很容易地通过:

来补救。
type ExpandBox<T extends Box<{}>> = Box<Expand<T>["value"]>;

type Expansion = ExpandBox<Target>;

然后展开上面的Target将导致

Playground

大部分功劳归于Cass

这篇关于如何让打字稿正确地显示这种类型?(带有泛型的递归类型)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆