TypeScript:根据输入数组元素推断 return 数组类型

TypeScript: infer return type of array based on input array elements

我有一个函数可以有多个 NodeRelationshipundefined 类型的参数。它具有以下签名:

export function useFormat(...elements: Array<Node | Relationship | undefined>) {
    const formattedElements: Array<FormattedNode | FormattedRelationship | undefined> = [];
    // do formatting...
    return formattedElements;
 }

我将其称为 React Hook,并根据给定的元素类型进行一些格式化。然后我 return 一个具有基本相同元素但格式不同的新数组:Array<FormattedNode | FormattedRelationship | undefined>.

我想知道 TypeScript 是否可以根据原始数组 elements.[=21] 中的相同元素推断 returned 数组中每个元素的类型=]

例如:原始数组 elements[0] 中的第一个元素是 Node,因此 returned 数组 formattedElements[0] 的第一个元素将是 FormattedNode.

我相信你应该在这里使用重载:

type Relationship = {
    type: 'Relationship'
}

type FormattedRelationship = {
    type: 'FormattedRelationship'
}

type CustomNode = {
    type: 'CustomNode '
}

type FormattedNode = {
    type: 'FormattedNode'
}


type Input = CustomNode | Relationship | undefined
type Output = FormattedNode | FormattedRelationship | undefined

type Return<T extends Input> =
    T extends CustomNode
    ? 0
    : T extends Relationship
    ? 1
    : T extends undefined
    ? 2
    : 3

function useFormat<
    T extends Input,
    U extends T[],
    R extends {
        0: FormattedNode[],
        1: FormattedRelationship[],
        2: undefined[],
        3: never
    }[Return<T>]>(...elements: [T, ...U]): R
function useFormat<
    T extends Input,
    U extends T[],
    >(...elements: [T, ...U]) {
    const formattedElements: Output[] = [];
    // do formatting...
    return formattedElements
}
const result = useFormat({ type: 'Relationship' }) // FormattedRelationship
const result2 = useFormat({ type: 'CustomNode ' }) // FormattedNode

可读性强的格式:


function useFormat<
    T extends CustomNode,
    U extends T[]>(...elements: [T, ...U]): FormattedNode[]
function useFormat<
    T extends Relationship,
    U extends T[]>(...elements: [T, ...U]): FormattedRelationship[]
function useFormat<
    T extends undefined,
    U extends T[]>(...elements: [T, ...U]): undefined[]
function useFormat<
    T extends Input,
    U extends T[],
    >(...elements: [T, ...U]) {
    const formattedElements: Output[] = [];
    // do formatting...
    return formattedElements
}
const result = useFormat({ type: 'Relationship' }) // FormattedRelationship
const result2 = useFormat({ type: 'CustomNode ' }) // FormattedNode

更新

type Relationship = {
    type: 'Relationship'
}

type FormattedRelationship = {
    type: 'FormattedRelationship'
}

type CustomNode = {
    type: 'CustomNode '
}

type FormattedNode = {
    type: 'FormattedNode'
}


type Input = CustomNode | Relationship | undefined
type Output = FormattedNode | FormattedRelationship | undefined

type MapPredicate<T> =
    T extends Input
    ? T extends CustomNode
    ? FormattedNode
    : T extends Relationship
    ? FormattedRelationship
    : T extends undefined
    ? undefined
    : never : never

type Mapped<
    Arr extends Array<unknown>,
    Result extends Array<unknown> = []
    > = Arr extends []
    ? []
    : Arr extends [infer H]
    ? [...Result, MapPredicate<H>]
    : Arr extends [infer Head, ...infer Tail]
    ? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
    : Readonly<Result>;

function useFormat<
    T extends Input,
    U extends T[],
    >(...elements: [...U]): Mapped<U>
function useFormat<
    T extends Input,
    U extends T[],
    >(...elements: [...U]): any {
    const formattedElements: Output[] = [];
    // do formatting...
    return formattedElements
}
const result = useFormat({ type: 'Relationship' }) // FormattedRelationship
const result2 = useFormat({ type: 'CustomNode ' }, { type: 'Relationship' }) // FormattedNode

请记住,这些类型不是 100% 安全的,因为我使用了 any 以使其与重载兼容。

其次,最好定义1个以上的重载。由于数组类型是可变的,有时很难在不使用类型断言或 any.

的情况下编写类型安全的函数

现在,你知道所有的缺点了。

Playground

如果你想了解更多关于文字数组映射或元组的知识,你可以参考我的blog

更新 - 没有休息

type Relationship = {
    type: 'Relationship'
}

type FormattedRelationship = {
    type: 'FormattedRelationship'
}

type CustomNode = {
    type: 'CustomNode '
}

type FormattedNode = {
    type: 'FormattedNode'
}


type Input = CustomNode | Relationship | undefined
type Output = FormattedNode | FormattedRelationship | undefined

type MapPredicate<T> =
    T extends Input
    ? T extends CustomNode
    ? FormattedNode
    : T extends Relationship
    ? FormattedRelationship
    : T extends undefined
    ? undefined
    : never : never

type Mapped<
    Arr extends ReadonlyArray<unknown>,
    Result extends Array<unknown> = []
    > = Arr extends []
    ? []
    : Arr extends [infer H]
    ? [...Result, MapPredicate<H>]
    : Arr extends [infer Head, ...infer Tail]
    ? Mapped<[...Tail], [...Result, MapPredicate<Head>]>
    : Readonly<Result>;

function useFormat<
    T extends Input,
    U extends ReadonlyArray<T>,
    >(elements: [...U]): Mapped<U>
function useFormat<
    T extends Input,
    U extends T[],
    >(elements: U): any {
    const formattedElements: Output[] = [];
    // do formatting...
    return formattedElements
}
const result = useFormat([{ type: 'Relationship' }]) // FormattedRelationship
const result2 = useFormat([{ type: 'CustomNode ' }, { type: 'Relationship' }]) // FormattedNode

Playground

当您需要 [...U] 而不是 U 时就是这种情况。它有助于推断数组中的每个元素。尝试在 overload

中用 U 替换 [...U]