typescript 键入一个函数,在给定另一个函数的情况下,该函数返回与给定函数类似的函数,但不带1个参数

wgeznvg7  于 2023-06-07  发布在  TypeScript
关注(0)|答案(1)|浏览(160)

我想创建一个函数(称为outerFn),它被赋予一个函数(称为innerFn)作为参数。innerFn函数被赋予一个对象类型参数,该对象将被要求具有user参数。然后outerFn应该返回一个函数(称为returnFn),该函数应该具有与innerFn类似的类型,但其参数对象不应该需要user参数(但仍然需要所有其他参数)。
我希望这样,我的outerFn可以返回一个returnFn,它实际上调用了innerFn,但自动注入了user(即而无需发送user
例如

type User = { user: { id: number, name: string } };

type Place = { Place: string };
type UserAndPlace = User & Place;
type InnerFn1Resolve = { something: string };
type InnerFn1 = (args: UserAndPlace) => Promise<InnerFn1Resolve>;
const innerFn1: InnerFn1 = async (args) => Promise.resolve({ something: 'yes' });
type ReturnFn1 = (args: Place) => Promise<InnerFn1Resolve>;
const returnFn1: ReturnFn1 = outerFn(innerFn1);

type Job = { job: number };
type UserAndJob = User & Job;
type InnerFn2Resolve = { else: number };
type InnerFn2 = (args: UserAndJob) => Promise<InnerFn2Resolve>;
const innerFn2: InnerFn2 = async (args) => Promise.resolve({ else: 1 });
type ReturnFn2 = (args: Job) => Promise<InnerFn2Resolve>;
const returnFn2: ReturnFn2 = outerFn(innerFn2);

我在outerFn上尝试了以下操作。上面的输入是正确的...但是outerFn返回抱怨。

type ArgsWithUser = Record<string, unknown> & User;
type InnerFn<Args extends ArgsWithUser, Response> = (args: Args) => Promise<Response>
type ReturnFn<Args extends ArgsWithUser, Response> = (args: Omit<Args, 'user'>) => Promise<Response>

const outerFn = <Args extends ArgsWithUser, Response>(innerFn: InnerFn<Args, Response>): ReturnFn<Args, Response> => {
    const user: User['user'] = {
        id: 1,
        name: 'Something',
    };
    return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};

对于innerFn的参数,我得到以下错误(即{ ...argsWithoutUser, user }
类型为'Omit<Args,“user”> & { user:“User; }”不能分配给“Args”类型的参数。'忽略<Args,“user”> & { user:User; }'可分配给类型为' Args '的约束,但'Args'可使用约束' ArgsWithUser '的不同子类型示例化。ts(2345)
我只是...真的不明白这个错误。我知道它试图说返回值可能不完全匹配Args......但我也不明白为什么会这样,感觉我一定是以某种方式混淆了类型。
我可以将对象强制转换为Args,但如果没有必要,我希望避免强制转换(我认为不应该这样做)。
这是一个TypeScript Playground重建,您可以在其中看到错误。
我特别想弄清楚的是 * 为什么 * 打字不正确。我已经使用TypeScript 3年多了,我认为我对为什么打字是错误的有一个很好的理解......但在这种情况下,我似乎无法理解为什么我有错。
任何帮助将不胜感激。

yhqotfr8

yhqotfr81#

你得到的错误在技术上是正确的。编译器不能确定innerFn的参数类型是否真的接受User作为其user属性。下面是编译器担心的情况的一个例子:

const innerFn = async (arg: {
    user: { id: number, name: "a" | "b" }
}) => ({ a: 1, b: 2 }[arg.user.name]).toFixed(2);

这里innerFn要求其参数的类型为{ user: { id: number, name: "a" | "b" } }。这是严格窄于User。如果尝试使用User类型的参数调用innerFn(),则很有可能会出现运行时错误。如果arg.user.name既不是"a"也不是"b",那么你将解引用undefined并得到一个TypeError
但是你的outerFn()的输入很乐意接受innerFn

const returnFn = outerFn(innerFn); // no compiler error

类型Args被推断为{user: {id: number; name: "a" | "b"}}Omit<Args, "user"> & { user: { id: number; name: string; }; }User相同。但这不是innerFn所需要的。就是问题是

//  'Omit<Args, "user"> & { user: { id: number; name: string; }; }' is assignable to the constraint of 
//  type 'Args', but 'Args' could be instantiated with a different subtype of constraint 'ArgsWithUser'.

因此,如果你运行returnFn(),你会得到运行时错误,没有编译器警告:

returnFn({}).catch(e => console.log(e)) // no compiler error, but:
// 💥 (intermediate value)[arg.user.name] is undefined

这就是问题所在这显然是不可能的,所以你可能只想Assert(你所谓的“转换”)并继续前进。
相反,如果你想重构以避免这个问题,你可以通过 * 添加 * User到一个类型而不是 * 省略 * 它来避免这个问题:

type InnerFn<A, R> = (args: A & User) => Promise<R>
type ReturnFn<A, R> = (args: A) => Promise<R>

const outerFn = <A, R>(innerFn: InnerFn<A, R>): ReturnFn<A, R> => {
    const user: User['user'] = {
        id: 1,
        name: 'Something',
    };
    return async (argsWithoutUser) => innerFn({ ...argsWithoutUser, user });
};

这编译起来没有错误,并且仍然适用于您所提供的示例。这可能对你的用例有用,也可能不起作用,但至少编译器是满意的。
Playground链接到代码

相关问题