typescript - 带有属性注入的打字稿高阶函数
问题描述
我想知道如何组合将对象作为唯一参数的函数,但每个高阶函数都附加一个属性。例如,我有一个接受上下文的函数。我想用 awithCookies
然后是一个函数来包装那个函数withToken
,并且可以访问这两个函数cookies
和token
最后一个函数。
withCookies(
withToken(
(ctx) => {
console.log(ctx.cookies) // Record<string, string>
console.log(ctx.token) // string
}
)
)
这种高阶函数的打字稿签名是什么?
解决方案
您当然可以为withCookies()
and编写调用签名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 });
当您使用 type 的参数withCookies
调用时,您会得到什么:withProps()
Cookies
const withCookies = withProps<Cookies>(
{ cookies: { a: "hello", b: "goodbye" } }
);
并且withToken
是withProps()
使用类型参数调用时得到的结果Token
:
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
是不可能的类型。两者的T
参数withCookies()
和withToken()
也被推断为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
另一种是显式指定一些泛型类型参数;withToken
在这种情况下,调用T
指定为Cookies
有效:
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"
*/
推荐阅读
- python - 一种更优雅的字典赋值方式
- kotlin-coroutines - Kotlin Coroutines Job 层次结构解释
- amazon-s3 - JavaScript GitHub Action 在 windows-latest 上运行以将目录或文件上传到 S3
- javascript - VSC 调试反应,无法创建断点/附加到进程
- reactjs - 调整材料表的工具栏高度
- python - Python loops - turtle object
- swift - Xcode 11.4 问题:环境中的上下文未连接到持久存储协调器
- python - 如何将自定义 numpy 数据类型数组的成员作为数组访问?
- android - 在真正的 android 设备上运行时,Ionic 4 android 应用程序未连接到本地节点 js 服务器
- node.js - 我们可以通过node js api执行java程序吗?