具有不兼容签名的联合类型 (2349) -- 这种递归类型是否可能?



我想构建一个看起来像这样的API:

export function GET({response}) {
return response
.match("text/html", "<p>Hello world</p>"),
.match("text/plain", "Hello world"),
.match("application/json", '{"message": "Hello world"}');
}

我有一个递归函数的开始:

function makeMatch(list) {
if(list.length === 0) {
return "end"
}
return {
match: item => makeMatch(list.filter(x => x !== item))
}
}

这是这样用的:

const items = ["a", "b", "c"];
makeMatch(items).match("a").match("b").match("c"); // -> "end"

以下是我试图用TypeScript实现的目标:

makeMatch(items).match("a"); // ok
makeMatch(items).match("x"); // does not type check, "x" is not in ["a", "b", "c"]
makeMatch(items).match("a").match("b").match("a") // does not type check, you already took "a"

我已经走到了这一步:

// makeMatch(emptyArray) returns "end"
function makeMatch<T>(list: []): "end"
// makeMatch(notEmptyArray) recurses
function makeMatch<T>(list: T[]): T extends "never" ? "end" : { match: <Item extends T>(item: Item) => ReturnType<typeof makeMatch<Exclude<T, Item>>> } 

function makeMatch<T>(list: T[])
{
if(list.length === 0) {
return "end"
}
return {
match: (item: T) => makeMatch(list.filter(x => x !== item))
}
}

但是当我尝试使用该功能时...

const x = makeMatch<"json" | "text" | "csv">(["json", "text", "csv"])

我收到此错误:

This expression is not callable.
Each member of the union type `'(<Item extends "json">(item: Item) => Exclude<"json", Item> extends "never" ? "end" : { match: <Item extends Exclude<"json", Item>>(item: Item) => Exclude<Exclude<"json", Item>, Item> extends "never" ? "end" : { ...; }; }) | (<Item extends "text">(item: Item) => Exclude<...> extends "never" ? "end" : { ...; }) | (<I...'` has signatures, but none of those signatures are compatible with each other.(2349)

我试图用 TypeScript 完成的事情可能吗?如果是这样,如何?

TypeScript 通常无法理解复杂的函数体逻辑。因此,当您返回多个不同的东西时,例如"end"

if (items.length === 0){
return "end"
}

也是一个具有函数的对象match()

return {
match: <I extends T>(item: I) => 
makeMatch<Exclude<T, I>>(items.filter((x): x is Exclude<T, I> => x !== item))
}

TypeScript 将无法遵循此条件逻辑,而只会推断两种情况的并集作为返回类型。因此,我们必须为函数创建自己的条件返回类型,该类型将能够根据输入区分结果。

泛型类型MakeMatchReturn涵盖这两种情况:

type MakeMatchReturn<T> = [T] extends [never]
? "end" 
: {
match: <I extends T>(item: I) => MakeMatchReturn<Exclude<T, I>>
}

T是传递数组的元素的联合。如果联合中没有剩余的元素,我们可以检查T是否扩展never.它包装在元组中以避免联合元素的分布。

生成的makeMatch函数将按如下方式实现:

function makeMatch<T extends string>(items: readonly T[]): MakeMatchReturn<T>  {
if (items.length === 0){
return "end" as unknown as MakeMatchReturn<T>
}

return {
match: <I extends T>(item: I) => 
makeMatch(items.filter((x): x is Exclude<T, I> => x !== item))
} as MakeMatchReturn<T>
}

T是传递给函数的数组元素的联合。还有泛型类型I它是传递给匹配函数的元素。当我们递归调用makeMatch函数时,我们必须使用ExcludeT中删除当前元素I

另请注意,我们必须在此处使用as const。否则,元组将被扩大为不再包含有关单个元素的任何信息的string[]

const items = ["a", "b", "c"] as const;
const result = makeMatch(items).match("a").match("b").match("c")
//    ^? "end"

操场

最新更新