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

通过Typescript中的其他函数参数推断函数参数的类型,typescript,Typescript,我想声明函数类型如下 type ReviewChangeHandler = | ((newReview: Review, oldReview: null, options: {type: 'CREATE'}) => void) | ((newReview: Review, oldReview: Review, options: {type: 'EDIT'}) => void) | ((newReview: null, oldReview: Review, o

我想声明函数类型如下

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

我怎样才能做到这样的行为呢?

这里发生了很多事情。首先,我将给出
Review
一个虚拟定义,以便我们可以使用它:

interface Review {
  name: string;
}

然后,您的联合类型的函数
ReviewChangeHandler
很遗憾对您没有用处。您大概不希望您的处理程序能够只处理其中一个参数列表,对吗?您希望它处理所有这些参数列表。也就是说,您希望类型为
ReviewChangeHandler
的函数是三个调用签名中的一个,而不是其中的一个:


TypeScript认为函数的交集与具有多个调用签名的函数相同;也就是说,这是一个函数。要编写这样一个函数,您可以声明多个调用签名,然后声明一个实现签名,该实现签名可以接受任何调用签名的输入并返回任何调用签名的输出。像这样:

// 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
}
通过将上述函数声明分配给新的
ReviewChangeHandler
交叉点类型的变量,您可以再次检查其是否与该类型匹配:

const check: ReviewChangeHandler = reviewChangeHandler; // okay

现在,您希望编译器能够根据
选项的类型推断
newReview
oldview
的较窄类型。type
。不幸的是,这不可能直接发生。编译器不会理解单独的联合类型变量是以这种方式关联的(有关缺少对相关联合类型的支持的讨论,请参阅)。相反,您可能希望将单独的联合类型变量打包到一个类型为的单个对象中

第一次尝试这样做可能是将
newReview
oldview
options
作为对象的属性:

  const reviewChangeAttempt = {
    newReview,
    oldReview,
    options
  };
但是您需要打开
查看更改尝试.options.type
,这是一个嵌套的判别属性。目前,TypeScript无法将这样的子属性识别为有效的判别式。有关支持此功能的功能请求,请参阅

如果我们想从有歧视的联盟中获益,我们需要将
选项。键入
子属性上移一级。因此,这给了我们以下类型:

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;
注意,我必须使用a来说服编译器
reviewChange
是有效的
ReviewChangeObj
。编译器无法跟踪
newReview
oldview
选项之间的相关性。type
意味着将这些选项重新打包为编译器确实理解的类型将导致编译器在没有类型断言的情况下进行对象操作

还要注意的是,这会丢弃您可能放入
选项中的任何其他内容
;如果有其他内容,可以通过使
ReviewChangeObj
元素同时具有
options
type
属性来保留它。但我离题了


在此之后,最后,您将得到您想要的推论:

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
语句中,编译器能够准确地理解
何时审阅更改。newReview
何时审阅更改。oldReview
将是
审阅
,何时将是
null



听起来您应该在这里使用函数重载而不是联合类型。@kaya3您能解释一下在这种情况下如何使用函数重载吗?我已经阅读了关于函数重载的typescript文档,但我无法将其应用到我的案例中。是否可以对函数类型泛型使用交集?例子:
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);
    }
  }
}