[英]TypeScript type for a function with a return type specified by an input parameter
I'm trying to specify a TypeScript type for a function that takes an input
and an optional outputType
parameter which determines the return type.我正在尝试为一个函数指定一个 TypeScript 类型,该函数接受一个
input
和一个确定返回类型的可选outputType
参数。 If no outputType
is specified the return type matches typeof input
.如果未指定
outputType
,则返回类型与typeof input
匹配。 The return value may also be a Promise
of that type.返回值也可能是该类型的
Promise
。
For example,例如,
type Decrypter = (options: {
input: string | Buffer | stream.Readable;
outputType?: "string" | "buffer" | "stream" | undefined;
}) => X | Promise<X>;
Where X
would be string
, Buffer
, or stream.Readable
depending on the value of outputType
, or the same type as options["input"]
if outputType
is undefined
.其中
X
将是string
、 Buffer
或stream.Readable
,具体取决于outputType
的值,或者如果outputType
为undefined
,则与options["input"]
的类型相同。
I feel like there is something that can be done with generics, extends
, and possibly infer
, but I can't quite get the right incantation!我觉得可以用泛型、
extends
和可能的infer
来做一些事情,但我不能完全得到正确的咒语!
Any ideas?有任何想法吗?
Below is an example of a basic implementation:下面是一个基本实现的例子:
const decrypter: Decrypter = async ({ input, outputType }) => {
let encrypted: string;
if (typeof input === "string") {
encrypted = input;
outputType ??= "string";
} else if (Buffer.isBuffer(input)) {
encrypted = input.toString("utf8");
outputType ??= "buffer";
} else if (Readable.isReadable(input)) {
const buffers = [];
for await (const buffer of input as Readable) {
buffers.push(buffer);
}
encrypted = Buffer.concat(buffers).toString("utf8");
outputType ??= "stream";
} else {
throw new Error("Invalid input");
}
const decrypted = encrypted.split("").reverse().join("");
switch (outputType) {
case "string":
return decrypted;
case "buffer":
return Buffer.from(decrypted);
case "stream":
return Readable.from(decrypted);
default:
throw new Error("Invalid outputType");
}
};
I'm using Node.js v18.4.0, TypeScript v4.7.4.我正在使用 Node.js v18.4.0、TypeScript v4.7.4。
The first thing we need is a map which maps from the string literal type to the return type.我们需要的第一件事是从字符串文字类型映射到返回类型的映射。
type TypeMap = {
string: string
buffer: Buffer
stream: stream.Readable
}
For both input
and output
we add the generic types I
and O
to capture the type of the object passed to the function.对于
input
和output
,我们添加通用类型I
和O
来捕获传递给函数的对象的类型。 We also constrain both generic types to their respective allowed inputs.我们还将这两种泛型类型限制为各自允许的输入。 We add
never
as the default type for O
if a type for outputType
was not provided.如果未提供
outputType
的类型,我们将never
添加为O
的默认类型。
type Decrypter = <
I extends TypeMap[keyof TypeMap],
O extends keyof TypeMap = never
>(options: {
input: I;
outputType?: O;
}) => DecryptorReturn<O, I>;
For the return type we check if any type was provided for O
.对于返回类型,我们检查是否为
O
提供了任何类型。 If yes, we can use the TypeMap
to lookup the type.如果是,我们可以使用
TypeMap
来查找类型。
type DecryptorReturn<O extends keyof TypeMap, I> = [O] extends [never]
? I | Promise<I>
: TypeMap[O] | Promise<TypeMap[O]>
Now some tests to see if it's working:现在进行一些测试以查看它是否有效:
const fn: Decrypter = {} as any
const res1 = fn({ input: "abc" })
// ^? const res1: "abc" | Promise<"abc">
const res2 = fn({ input: "abc", outputType: "buffer" })
// ^? const res2: Buffer | Promise<Buffer>
Looks good to me.在我看来很好。
When you writing functions with complex generic types, TypeScript will always have problems understanding the logic of your function implementation.当您编写具有复杂泛型类型的函数时,TypeScript 在理解函数实现的逻辑时总是会遇到问题。 For example, you try to assign a
string
to outputType
.例如,您尝试将
string
分配给outputType
。 But since outputType
is of type O
TypeScript will net let you assign anything to it.但是由于
outputType
是O
类型,TypeScript 将允许你为它分配任何东西。
So have some short-comings when trying to type the function itself.所以在尝试键入函数本身时会有一些缺点。 First of all, I would give
input
and outputType
explicit types which are not generic so that you can assign things to them.首先,我会给出非通用的
input
和outputType
显式类型,以便您可以为它们分配东西。
(async ({ input, outputType }: {input: TypeMap[keyof TypeMap], outputType: keyof TypeMap}) => { ... }
TypeScript will now have problems with assigning this function to the type Decrypter
because it is not generic. TypeScript 现在在将此函数分配给类型
Decrypter
时会遇到问题,因为它不是通用的。 I would use a simple type assertion to silence this error.我会使用一个简单的类型断言来消除这个错误。
const decrypter: Decrypter = (async ({ input, outputType }: {input: TypeMap[keyof TypeMap], outputType: keyof TypeMap}) => {
/* ... */
}) as Decrypter
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.