我正在尝试创建一个泛型,如下所示:
type HTTPMethod = "HEAD" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
type Endpoint<M extends HTTPMethod, S extends {}> = () => void;
“想法”是在声明Endpoint
函数时提供形状S
。S
可以用作输入/输出类型。
但是,我希望S
对于POST、PUT、PATCH是必需的,但是对于HEAD、GET、DELETE是可选的。类似于这样:
const a: Endpoint = () => {}; // < requires 1 type argument(s).
const b: Endpoint<"HEAD"> = () => {};
const c: Endpoint<"HEAD", {}> = () => {};
const d: Endpoint<"POST"> = () => {}; // < requires 2 type argument(s).
const e: Endpoint<"POST", {}> = () => {};
看起来好像使用extends
和=
并不像我预期的那样工作。
type Test<T extends string = number> = () => void;
错误,因为number不满足string。但是:
type Endpoint<M extends HTTPMethod, S extends M extends "POST" | "PUT" | "PATCH"
? S extends unknown
? never
: {}
: never = unknown> = () => void;
- 总是有效的,尽管事实上
unknown
不是never
也不是{}
。我已经玩了几个小时以上,但我似乎找不到什么工作。
这样的事情在当前的TypeScript中可能吗?
1条答案
按热度按时间kjthegm61#
TypeScript中的Generic类型参数可以有默认的类型参数,但是如果你使用它们,类型参数是可选的,如果你不使用类型参数,类型参数是必需的。在TypeScript中没有“条件可选”的类型参数。这意味着虽然可能获得与你想要的类似的行为,但没有任何功能可以提供它。
一个合理的解决方法是使用单个元组类型的类型参数,而不是可变数量的类型参数。因此,您可以编写
Endpoint<X, Y>
而不是Endpoint<[X, Y]>
。与泛型类型参数相比,对可变长度元组的支持要多得多,并且其长度取决于某些条件。这里有一个方法可以做到这一点:
所以
Allowed
是两个元素的元组类型的并集,其中第二个元素是可选的,取决于第一个元素类型。然后,您可以将
Endpoint
定义为将其类型参数约束为Allowed
:这就是你想要的,缺点是它需要一个元组 Package 器,但优点是它相当简单,你可以只看
Allowed
,然后读出你应该做什么和不应该做什么。另一种解决方法是让所需的错误出现在 first 类型参数上,而不是“丢失”的第二个参数上。也就是说,我们不能让第二个参数“有条件可选”,但我们可以约束第一个类型参数,这样第二个类型参数的不需要的值会使第一个类型参数无法满足其约束。
它可能看起来像这样:
因此,第一个类型参数
T
被约束为依赖于它自己和第二个类型参数U
的某个参数。如果是的话,我们可以把它移到第三个类型参数......但是我离题了)。我假设如果第二个类型参数U
是never
,那么它就和missing一样(如果你需要区分never
和missing
,那么我们可以创建一些“sigil”类型,但是这更奇怪)。因此,
T
的约束为HTTPMethod
或Exclude<HTTPMethod, NeedExtra>
(不是NeedExtra
的所有内容),取决于T
和U
。如果T
可分配给NeedExtra
,但U
是never
,那么就有问题了,我们把T
约束为所有不是NeedExtra
的东西,这会失败,否则就没有问题,我们把T
约束为HTTPMethod
。我们试试看:
看起来也不错。错误是合理的,并且具有非常接近你想要的优点。缺点是约束更加神秘,并且绝对不是 * 旨在 * 由语言支持。我可以想象未来TypeScript版本的更改将打破这一点并需要重构(特别是循环)。但目前它工作正常,并不可怕。
Playground代码链接