[英]Infer type of function argument by other function argument in Typescript
I want to declare function type like我想声明 function 类型像
type ReviewChangeHandler =
| ((newReview: Review, oldReview: null, options: {type: 'CREATE'}) => void)
| ((newReview: Review, oldReview: Review, options: {type: 'EDIT'}) => void)
| ((newReview: null, oldReview: Review, options: {type: 'DELETE') => void)
I want the type of newReview
and oldReview
argument to be inferred by the value of options.type
, so I can write code like:我希望
newReview
和oldReview
参数的类型由options.type
的值推断,所以我可以编写如下代码:
switch (options.type) {
case 'CREATE':
// `newReview` inferred to type `Review` and `oldReview` inferred to type `null`
case 'EDIT':
// `newReview` inferred to type `Review` and `oldReview` inferred to type `Review`
case 'DELETE:
// `newReview` inferred to type `null` and `oldReview` inferred to type `Review`
How can I achieve such behavior?我怎样才能实现这种行为?
There's a bunch going on here.这里有一堆事情。 First of all, I'm going to give
Review
a dummy definition so we can work with it:首先,我将给
Review
一个虚拟定义,以便我们可以使用它:
interface Review {
name: string;
}
Then, your union type of functions ReviewChangeHandler
is unfortunately not going to be useful to you.然后,不幸的是,您的联合类型的函数
ReviewChangeHandler
对您没有用处。 You presumably don't want your handler to be able to handle just one of those parameter lists, right?您可能不希望您的处理程序只能处理其中一个参数列表,对吧? You want it to handle all of those parameter lists.
您希望它处理所有这些参数列表。 That is, you want a function of type
ReviewChangeHandler
to be the intersection of the three call signatures, not the union of them:也就是说,您希望 ReviewChangeHandler 类型的
ReviewChangeHandler
成为三个调用签名的交集,而不是它们的并集:
type ReviewChangeHandler =
& ((newReview: Review, oldReview: null, options: { type: 'CREATE' }) => void)
& ((newReview: Review, oldReview: Review, options: { type: 'EDIT' }) => void)
& ((newReview: null, oldReview: Review, options: { type: 'DELETE' }) => void)
TypeScript considers the intersection of functions to be the same as a function with multiple call signatures; TypeScript 认为函数的交集与具有多个调用签名的 function 相同; that is, it's an overloaded function.
也就是说,它是一个过载的 function。 To write such a function you can declare multiple call signatures and then a single implementation signature that can accept any of the call signatures' inputs and return any of the call signatures' outputs.
要编写这样的 function,您可以声明多个调用签名,然后声明一个可以接受任何调用签名输入并返回任何调用签名输出的实现签名。 Like this:
像这样:
// three call signatures
function reviewChangeHandler(newReview: Review, oldReview: null, options: { type: "CREATE" }): void;
function reviewChangeHandler(newReview: Review, oldReview: Review, options: { type: "EDIT" }): void;
function reviewChangeHandler(newReview: null, oldReview: Review, options: { type: "DELETE" }): void;
// one implementation signature
function reviewChangeHandler(newReview: Review | null, oldReview: Review | null, options: { type: "CREATE" } | { type: "EDIT" } | { type: "DELETE" }) {
// impl here
}
You can double check that the above function declarations matches the new ReviewChangeHandler
intersection type by assigning it to a variable of that type:您可以通过将其分配给该类型的变量来仔细检查上述 function 声明是否与新的
ReviewChangeHandler
交集类型匹配:
const check: ReviewChangeHandler = reviewChangeHandler; // okay
Now you would like the compiler to be able to infer narrower types for newReview
and oldReview
based on the type of options.type
.现在您希望编译器能够根据
options.type
的类型为newReview
和oldReview
推断出更窄的类型。 Unfortunately this cannot really happen directly.不幸的是,这不能真正直接发生。 The compiler will not understand that separate union-typed variables are correlated this way (see microsoft/TypeScript#30581 for discussion on the lack of support for correlated union types).
编译器不会理解单独的联合类型变量以这种方式相关(请参阅microsoft/TypeScript#30581以了解缺乏对相关联合类型的支持的讨论)。 Instead you might want to package your separate union-typed variables into a single object of a discriminated union type.
相反,您可能希望 package 将您单独的联合类型变量转换为可区分联合类型的单个 object。
A plausible first attempt at this would be to just put newReview
, oldReview
, and options
as properties of an object:一个合理的第一次尝试是将
newReview
、 oldReview
和options
作为 object 的属性:
const reviewChangeAttempt = {
newReview,
oldReview,
options
};
But you'd need to switch
on reviewChangeAttempt.options.type
, a nested discriminant property.但是你需要
switch
reviewChangeAttempt.options.type
,一个嵌套的判别属性。 And currently TypeScript does not recognize subproperties like this as valid discriminants.目前 TypeScript 不能将这样的子属性识别为有效的判别式。 See microsoft/TypeScript#18758 for a feature request to support this.
有关支持此功能的功能请求,请参阅microsoft/TypeScript#18758 。
If we want to gain the benefits of a discriminated union, we need to move the options.type
subproperty up one level.如果我们想获得歧视联合的好处,我们需要将
options.type
子属性上移一级。 So this gives us the following type:所以这给了我们以下类型:
type ReviewChangeObj = {
newReview: Review;
oldReview: null;
type: "CREATE";
} | {
newReview: Review;
oldReview: Review;
type: "EDIT";
} | {
newReview: null;
oldReview: Review;
type: "DELETE";
}
and a possible implementation like this:以及像这样的可能实现:
const reviewChange = {
newReview,
oldReview,
type: options.type
} as ReviewChangeObj;
Note that I have to use a type assertion to convince the compiler that reviewChange
is a valid ReviewChangeObj
.请注意,我必须使用类型断言来说服编译器
reviewChange
是有效的ReviewChangeObj
。 The compiler's lack of ability to follow the correlation between newReview
, oldReview
, and options.type
means that any repackaging of these into a type the compiler does understand would cause the compiler to object without something like a type assertion.编译器无法跟踪
newReview
、 oldReview
和options.type
之间的相关性,这意味着将它们重新打包成编译器确实理解的类型会导致编译器在没有类型断言的情况下生成 object。
Also note that this throws away anything else that you might have put into options
;另请注意,这会丢弃您可能已放入
options
的任何其他内容; if you have other stuff in there you can hold onto it by making ReviewChangeObj
elements have both an options
and a type
property.如果你有其他东西,你可以通过让
ReviewChangeObj
元素同时具有options
和type
属性来保留它。 But I digress.但我离题了。
After this, finally, you get the inference you want:在此之后,最后,你得到你想要的推断:
function reviewChangeHandler(newReview: Review, oldReview: null, options: { type: "CREATE" }): void;
function reviewChangeHandler(newReview: Review, oldReview: Review, options: { type: "EDIT" }): void;
function reviewChangeHandler(newReview: null, oldReview: Review, options: { type: "DELETE" }): void;
function reviewChangeHandler(newReview: Review | null, oldReview: Review | null, options: { type: "CREATE" } | { type: "EDIT" } | { type: "DELETE" }) {
const reviewChange = {
newReview,
oldReview,
type: options.type
} as ReviewChangeObj;
switch (reviewChange.type) {
case 'CREATE': {
console.log("CREATING NEW REVIEW: " + reviewChange.newReview.name);
break;
}
case 'EDIT': {
console.log("EDITING OLD REVIEW: " + reviewChange.oldReview.name + " AND NEW REVIEW: " + reviewChange.newReview.name);
break;
}
case 'DELETE': {
console.log("DELETING OLD REVIEW: " + reviewChange.oldReview.name);
}
}
}
You can see that inside the case
statements the compiler understands exactly when reviewChange.newReview
and reviewChange.oldReview
are going to be Review
and when they are going to be null
.您可以看到,在
case
语句中,编译器准确地理解了reviewChange.newReview
和reviewChange.oldReview
成为Review
以及何时成为null
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.