我想知道如何组合以对象为唯一参数的函数,但每个高阶函数都会附加一个属性。例如,我有一个接受上下文的函数。我想用一个withCookies
,然后是一个withToken
函数来包装这个函数,并可以访问最后一个函数的cookies
和token
。
withCookies(
withToken(
(ctx) => {
console.log(ctx.cookies) // Record<string, string>
console.log(ctx.token) // string
}
)
)
这种高阶函数的打字稿签名是什么?
你当然可以为withCookies()
和withToken()
编写调用签名,甚至可以编写一个可以生成这两个withProps()
函数。 但是您将遇到的一个问题是类型推断:编译器可能无法在最里面的函数中上下文键入ctx
回调参数,因为有问题的函数必须是泛型的,而泛型类型参数推断通常不能很好地与上下文类型配合使用(有关此限制的示例,请参阅 microsoft/TypeScript#33042 和 microsoft/TypeScript#38872 等问题)。 为了使代码正常工作,您可能需要在此过程中手动指定一些类型,以便编译器了解您正在执行的操作。
首先,让我们定义Cookies
和Token
接口,以便为正在谈论的对象命名:
interface Cookies {
cookies: Record<string, string>
}
interface Token {
token: string;
}
然后,withCookies()
函数在某些对象类型T
中应该是通用的。 它接受一个类型为(ctx: T & Cookies) => void
的回调函数,并返回一个类型为(ctx: T) => void
的函数。 喜欢这个:
/*
declare const withCookies: <T extends object>(
cb: (ctx: T & Cookies) => void
) => (ctx: T) => void
*/
withToken()
具有类似的类型,其中Cookies
被替换为Token
:
/*
declare const withToken: <T extends object>(
cb: (ctx: T & Token) => void
) => (ctx: T) => void
*/
签名如此相似意味着您可能有一个可以吐出这些签名的withProps()
函数。 这是编写它的一种方法:
const withProps = <U extends object>(u: U) =>
<T extends object>(cb: (ctx: T & U) => void) =>
(ctx: T) => cb({ ...ctx, ...u });
因此,当您使用类型Cookies
的参数调用withProps()
时,您会得到withCookies
:
const withCookies = withProps<Cookies>(
{ cookies: { a: "hello", b: "goodbye" } }
);
withToken
是使用类型为Token
的参数调用withProps()
时得到的
const withToken = withProps<Token>(
{ token: "token" }
);
您可以验证这两个函数是否具有上面声明的调用签名。
所以现在我们尝试调用这些函数,看到编译器对上下文类型不满意:
const badResult = withCookies(
withToken(
(ctx) => {
console.log(ctx.cookies) // error! Property 'cookies' does not exist on type 'never'
console.log(ctx.token) // error! Property 'token' does not exist on type 'never'
}
)
);
// const badResult: (ctx: never) => void
哎呀,编译器推断内部ctx
是类型never
,不可能的类型。还推断withCookies()
和withToken()
的T
参数为never
。 泛型和上下文类型推断的交互在这里没有用处,因此回调的主体有错误。 虽然badResult()
在运行时会正常运行,但最好清除编译器错误。
修复错误的一种方法是将ctx
回调参数的类型显式注释为Cookies & Token
:
const result = withCookies(
withToken(
(ctx: Cookies & Token) => {
console.log(ctx.cookies)
console.log(ctx.token)
}
)
);
// const result: (ctx: object) => void
另一种方法是显式指定一些泛型类型参数;在这种情况下,调用T
指定为Cookies
的withToken
是有效的:
const alsoResult = withCookies(
withToken<Cookies>(
(ctx) => {
console.log(ctx.cookies)
console.log(ctx.token)
}
)
);
// const alsoResult: (ctx: object) => void
无论哪种方式都会生成一个接受任何对象的最终函数。 当我们测试它时,我们可以看到传递给withProps()
的对象已经根据需要传播到我们的内部回调中:
result({});
/*
[LOG]: {
"a": "hello",
"b": "goodbye"
}
[LOG]: "token"
*/
>游乐场链接到代码