Javascript 组合泛型函数参数类型

Javascript 组合泛型函数参数类型,javascript,typescript,typescript-typings,Javascript,Typescript,Typescript Typings,我希望typescript接受具有类似结构但具有不同值的参数列表,并正确推断生成的联合类型 示例代码 interface User< N extends string = string, S extends unknown = unknown, A extends (a: S) => unknown = (a: S) => unknown > { name: N; state: S; action: A; } functi

我希望typescript接受具有类似结构但具有不同值的参数列表,并正确推断生成的联合类型

示例代码

interface User<
    N extends string = string,
    S extends unknown = unknown,
    A extends (a: S) => unknown = (a: S) => unknown
> {
    name: N;
    state: S;
    action: A;
}

function createUser<
    N extends string = string,
    S extends unknown = unknown,
    A extends (a: S) => unknown = (a: S) => unknown
>(name: N, state: S, action: A): User<N, S, A> {
    return { action, name, state };
}

function combineUsers<
    V extends User<string, unknown, (a: unknown) => unknown>[]
>(...users: V) {
    type Names = V[number]["name"];
    return {
        actions: users.reduce(
            (a, u) => ({ ...a, [u.name]: u.action }),
            {} as { [K in Names]: Extract<V[number], { name: K }>["action"] },
        ),
        names: new Set<Names>(users.map((u) => u.name)),
    };
}

const userA = createUser("user1", 1, (_a: number) => undefined);
const user2 = createUser("userB", "two", (_s: string) => true);
const users = combineUsers(userA, user2);
// Argument of type 'User<"user1", number, (_a: number) => undefined>' is not assignable to parameter of type 'User<string, unknown, (a: unknown) => unknown>'.
//   Type '(_a: number) => undefined' is not assignable to type '(a: unknown) => unknown'.
//     Types of parameters '_a' and 'a' are incompatible.
//       Type 'unknown' is not assignable to type 'number'.ts(2345)
用户界面<
N扩展字符串=字符串,
S扩展未知=未知,
A扩展(A:S)=>unknown=(A:S)=>unknown
> {
姓名:N;
国家:S;
行动:A;
}
函数createUser<
N扩展字符串=字符串,
S扩展未知=未知,
A扩展(A:S)=>unknown=(A:S)=>unknown
>(名称:N,状态:S,操作:A):用户{
返回{action,name,state};
}
功能组合器<
V扩展用户未知>[]
>(…用户:V){
类型名称=V[编号][“名称”];
返回{
操作:users.reduce(
(a,u)=>({…a,[u.name]:u.action}),
{}作为{[K in Names]:提取[“action”]},
),
名称:新集合(users.map((u)=>u.name)),
};
}
const userA=createUser(“user1”,1,(\u a:number)=>未定义);
constuser2=createUser(“userB”,“two”,(\s:string)=>true);
const users=组合用户(userA,user2);
//“User undefined>”类型的参数不能分配给“User unknown>”类型的参数。
//类型“(\u a:number)=>undefined”不可分配给类型“(a:unknown)=>unknown”。
//参数“_a”和“a”的类型不兼容。
//类型“未知”不可分配给类型“编号”。ts(2345)
这就是由于函数不兼容而导致的编译错误

我希望看到的是函数返回的类型是某种推断的并集

type Return = {
    names: Set<"user1" | "userB">,
    actions: {
        user1: (_a: number) => undefined;
        userB: (_s: string) => boolean;
    }
}
类型返回={
名称:Set,
行动:{
user1:(_a:number)=>未定义;
userB:(_s:string)=>布尔值;
}
}
  • 这可能吗?可以洒任何东西,但我不想
  • 有没有其他办法或更好的方法来实现这一点
编辑:


@jcalz的解决方案对于上一版本中所述的问题非常有效,但我的示例缺少了一些东西,使其适用于我的实际代码,即当函数的参数必须与其他地方定义的类型匹配时。

我将给
组合用户的类型如下所示:

function combineUsers<V extends User<string, never[], unknown>[]>(...users: V) {
  type Names = V[number]['name'];
  return {
    actions: users.reduce(
      (a, u) => ({ ...a, [u.name]: u.action }),
      {} as { [K in Names]: Extract<V[number], { name: K }>['action'] }
    ),
    names: new Set<Names>(users.map((u) => u.name)),
  };
}
然后您
combineuser(userA,user2)
会产生以下结果:

const users = combineUsers(userA, user2);
/* const users: {
    actions: {
        user1: (x: number, y: string) => undefined;
        userB: (x: string) => boolean;
    };
    names: Set<"user1" | "userB">;
} */
const users=combineuser(userA,user2);
/*const用户:{
行动:{
user1:(x:number,y:string)=>未定义;
userB:(x:string)=>布尔值;
};
名称:集;
} */
看起来不错


对此我有一个答案,但是。。。在
createUser()
中,
fnArgs
是否只是一个伪参数,用于向编译器提示
操作
方法将采用的参数类型?看起来确实如此,但有什么意义呢,当所讨论的方法不管发生什么都只返回
returnValue
值时?当您可以只使用
(…args:A)=>R
时,为什么会出现
(…args:A)=>R
的复杂性?或者,为什么
createUser
实际上不接受
类型的函数(…args:a)=>R
,而不是
a
R
类型的值?这是一个奇怪/令人分心的例子。你能修改它吗?当然,你能修改它。我只是想创建一个精简版的实际代码。本例中的许多内容对于给出问题上下文来说是多余的。谢谢!但遗憾的是,我在最初的问题陈述中漏掉了一些东西,我现在把它添加到了问题中。但这确实给我指明了正确的方向。如果你想查看更新,那也太好了!
const users = combineUsers(userA, user2);
/* const users: {
    actions: {
        user1: (x: number, y: string) => undefined;
        userB: (x: string) => boolean;
    };
    names: Set<"user1" | "userB">;
} */