【问题标题】:Combining generic function argument types组合通用函数参数类型
【发布时间】:2020-11-15 06:51:42
【问题描述】:

我希望 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)

这是我得到的编译错误,因为不兼容的功能。

我希望看到函数返回的类型是一些推断的联合,例如;

type Return = {
    names: Set<"user1" | "userB">,
    actions: {
        user1: (_a: number) => undefined;
        userB: (_s: string) => boolean;
    }
}
  • 这甚至可能吗?可以洒any,但我宁愿不要
  • 是否有任何替代方案或更好的方法来实现这一目标

编辑:

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

【问题讨论】:

  • 我对此有一个答案,但是...在createUser() 中,isfnArgs 只是一个虚拟参数,用于向编译器提示action 方法将采用的参数类型?它确实看起来像,但有什么意义,当有问题的方法无论如何都只会返回 returnValue 值时?为什么(...args: A)=&gt;R 的复杂性还不如只使用() =&gt; R?或者,为什么createUser 实际上不采用(...args: A)=&gt;R 类型的函数 而不是AR 类型的?这是一个奇怪/分散注意力的例子。可以修改吗?
  • 可以,可以修改。只是试图创建我的实际代码的精简版本。这个例子中的很多东西对于给出问题的上下文是多余的。

标签: javascript typescript typescript-typings


【解决方案1】:

我要给combinedUsers 的输入是这样的:

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)),
  };
}

请注意,只有一个泛型类型参数V,对应于传入的User&lt;N, A, R&gt;对象数组。类型参数string(属性类型的协变上限约束),never[](逆变下限)参数类型的边界约束)和unknown(返回值类型的协变上限约束)比anyanyany 更安全,但仍然允许一些奇怪的输入。希望这实际上并不重要。

在实现中,我们定义Names 类型来获取V 的每个元素的N 参数的联合(V[number]['name'] 是当您使用number 索引索引users 时获得的类型然后使用"name" 索引到生成的User 中)。

actions 属性的类型是一个映射类型,其中对于Names 中的每个名称,我们找到V 的元素,这些元素将其作为name 并获取其action 类型。


让我们看看它是否有效。假设您有以下类型的 userAuser2

//const userA: User<"user1", [x: number, y: string], undefined>
//const user2: User<"userB", [x: string], boolean>

然后你combineUsers(userA, user2) 会导致:

const users = combineUsers(userA, user2);
/* const users: {
    actions: {
        user1: (x: number, y: string) => undefined;
        userB: (x: string) => boolean;
    };
    names: Set<"user1" | "userB">;
} */

看起来不错。

Playground link to code

【讨论】:

  • 谢谢!但遗憾的是,我错过了我现在添加到问题中的原始问题陈述中的一些内容。但这确实为我指明了正确的方向。如果您想查看更新,那也很棒!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-03
  • 1970-01-01
  • 2016-01-25
  • 1970-01-01
相关资源
最近更新 更多