带有属性注入的打字稿高阶函数



我想知道如何组合以对象为唯一参数的函数,但每个高阶函数都会附加一个属性。例如,我有一个接受上下文的函数。我想用一个withCookies,然后是一个withToken函数来包装这个函数,并可以访问最后一个函数的cookiestoken


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 等问题)。 为了使代码正常工作,您可能需要在此过程中手动指定一些类型,以便编译器了解您正在执行的操作。


首先,让我们定义CookiesToken接口,以便为正在谈论的对象命名:

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指定为CookieswithToken是有效的:

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" 
*/
>游乐场链接到代码

相关内容

  • 没有找到相关文章

最新更新