繁体   English   中英

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

[英]Infer type of function argument by other function argument in Typescript

我想声明 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)

我希望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`

我怎样才能实现这种行为?

这里有一堆事情。 首先,我将给Review一个虚拟定义,以便我们可以使用它:

interface Review {
  name: string;
}

然后,不幸的是,您的联合类型的函数ReviewChangeHandler对您没有用处。 您可能不希望您的处理程序只能处理其中一个参数列表,对吧? 您希望它处理所有这些参数列表。 也就是说,您希望 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 认为函数的交集与具有多个调用签名的 function 相同; 也就是说,它是一个过载的 function。 要编写这样的 function,您可以声明多个调用签名,然后声明一个可以接受任何调用签名输入并返回任何调用签名输出的实现签名。 像这样:

// 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
}

您可以通过将其分配给该类型的变量来仔细检查上述 function 声明是否与新的ReviewChangeHandler交集类型匹配:

const check: ReviewChangeHandler = reviewChangeHandler; // okay

现在您希望编译器能够根据options.type的类型为newReviewoldReview推断出更窄的类型。 不幸的是,这不能真正直接发生。 编译器不会理解单独的联合类型变量以这种方式相关(请参阅microsoft/TypeScript#30581以了解缺乏对相关联合类型的支持的讨论)。 相反,您可能希望 package 将您单独的联合类型变量转换为可区分联合类型的单个 object。

一个合理的第一次尝试是将newReviewoldReviewoptions作为 object 的属性:

  const reviewChangeAttempt = {
    newReview,
    oldReview,
    options
  };

但是你需要switch reviewChangeAttempt.options.type ,一个嵌套的判别属性。 目前 TypeScript 不能将这样的子属性识别为有效的判别式。 有关支持此功能的功能请求,请参阅microsoft/TypeScript#18758

如果我们想获得歧视联合的好处,我们需要将options.type子属性上移一级。 所以这给了我们以下类型:

type ReviewChangeObj = {
  newReview: Review;
  oldReview: null;
  type: "CREATE";
} | {
  newReview: Review;
  oldReview: Review;
  type: "EDIT";
} | {
  newReview: null;
  oldReview: Review;
  type: "DELETE";
}

以及像这样的可能实现:

  const reviewChange = {
    newReview,
    oldReview,
    type: options.type
  } as ReviewChangeObj;

请注意,我必须使用类型断言来说服编译器reviewChange是有效的ReviewChangeObj 编译器无法跟踪newReviewoldReviewoptions.type之间的相关性,这意味着将它们重新打包成编译器确实理解的类型会导致编译器在没有类型断言的情况下生成 object。

另请注意,这会丢弃您可能已放入options的任何其他内容; 如果你有其他东西,你可以通过让ReviewChangeObj元素同时具有optionstype属性来保留它。 但我离题了。


在此之后,最后,你得到你想要的推断:

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);
    }
  }
}

您可以看到,在case语句中,编译器准确地理解了reviewChange.newReviewreviewChange.oldReview成为Review以及何时成为null


Playground 代码链接

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM