简体   繁体   English

通过 Typescript 中的其他 function 参数推断 function 参数的类型

[英]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:我希望newReviewoldReview参数的类型由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的类型为newReviewoldReview推断出更窄的类型。 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:一个合理的第一次尝试是将newReviewoldReviewoptions作为 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.编译器无法跟踪newReviewoldReviewoptions.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元素同时具有optionstype属性来保留它。 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.newReviewreviewChange.oldReview成为Review以及何时成为null


Playground link to code Playground 代码链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从打字稿中的功能参数推断类型参数 - infer type argument from function argument in typescript 打字稿:推断函数参数 - Typescript: Infer function argument TypeScript 推断函数第一个参数的第二个参数 - TypeScript infer second argument of a function first argument 从TypeScript中的函数的参数类型推断通用类型参数 - Infer generic type argument from parameter type of function in TypeScript Typescript function 作为参数 - 使用输入的 ZDBC11CAA5BDA28F77E6FB4EDABD8 推断 output 类型 - Typescript function as argument - infer output type using inputted arguments 通用接口内的 Typescript 函数来推断参数类型 - Typescript function inside generic interface to infer argument type 基于 TypeScript 中具有区分联合的其他参数推断 function 参数的类型 - Infering type of function argument based on other argument with discriminated union in TypeScript TypeScript:当提供变量 CLASS 作为函数参数时,将返回类型推断为该类的实例(仅从参数中) - TypeScript: when supplying a variable CLASS as a function argument, infer the return type to be an instance of that class (purely from argument alone) 在打字稿中,如何从柯里化函数中第二个函数调用的参数类型推断参数类型 - in typescript, how to infer the parameter type from the argument type of the 2nd function call in a curried function TypeScript - 根据来自另一个 function 的返回类型推断函数的参数类型 - TypeScript - infer function's argument type based on return type from another function
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM