如何修复具有多个功能的接口的 "No overload matches this call"

How do I fix an "No overload matches this call" of an interface with multiple functions

这是我正在努力处理的代码:

const enum labelKey {
  RATING = 'asset.ratingPercentage',
  EPISODE_TITLE = 'episode.title.count.short',
  EPISODE_SERIES_TITLE = 'episode.title.series.count.short',
}

interface GetLabel {
  (key: labelKey.RATING, options: { rating: string }): string;
  (
    key: labelKey.EPISODE_TITLE,
    options: { episodeNumber: number; seasonNumber: number; episodeTitle: string },
  ): string;
  (
    key: labelKey.EPISODE_SERIES_TITLE,
    options: { episodeNumber: number; seasonNumber: number; seriesName: string },
  ): string;
  (key: string): string;
}

const getLabel: GetLabel = (key: string, options?: { [key: string]: unknown }) => {
  return '';
};

type Label = {
  key: labelKey.RATING;
  options: { rating: string };
} | {
  key: labelKey.EPISODE_TITLE;
  options: { episodeNumber: number; seasonNumber: number; episodeTitle: string };
} | {
  key: labelKey.EPISODE_SERIES_TITLE;
  options: { episodeNumber: number; seasonNumber: number; seriesName: string };
};

function createLabel(someCondition: boolean): Label {
  if (someCondition) {
    return {
      key: labelKey.RATING,
      options: { rating: '5' },
    };
  }
  else {
    return {
      key: labelKey.EPISODE_TITLE,
      options: { episodeNumber: 1, seasonNumber: 2, episodeTitle: 'Hello'  },
    };
  }
}

const label = createLabel(false);

getLabel(label.key, label.options);

我什至添加了一个 labelKey 枚举(我认为这不是必需的)来为 TypeScript 提供提示,但 getLabel 仍然不起作用。

关于如何实现的任何想法?

这是 Playground

如果将 label 的定义更改为

const label = {
  key: labelKey.RATING,
  options: { rating: '5' },
} as const;

key 属性 的推断类型将是 labelKey.RATING 而不是 labelKey 并且您不会得到此错误

这是Playground

--

编辑: 您可以根据 Label

的定义使用泛型替换重载
interface GetLabel {
  <K extends labelKey>(key: K, options: Extract<Label, {key: K}>['options']): string;
  (key: string): string;
}

playground

问题的根源是你在调用getLabel时不知道label.key的类型。因此,前三个重载中的 none 可以匹配,因为它们都需要特定的键类型。但是你不能匹配第四个 string 重载,因为这个重载 (key: string): string; 要求你不传递第二个参数。

解决方案 1:getLabel

让我们添加一个重载,我们知道我们有 labelKey 但不知道是哪一个。因此,我们应该接受任何重载的 options,而不要求选项类型与键类型匹配。然后作为最后的手段,您可以添加另一个接受任何 string 和任何 options 对象的重载。

interface GetLabel {
...exisiting overloads 1-3...
  (key: Label['key'], options: Label['options']): string;
  (key: string, options: Record<string, any>): string;
}

解决方案 2:createLabel

但我们也可以通过 createLabel 函数来解决这个问题。目前 Typescript 不知道 Label 这个函数 return 是哪个类型的。但是我们应该能够根据我们调用函数的 someCondition 的值知道我们将得到哪个 Label 。所以我们实际上可以在这里添加重载。注意:当你有一个明确的 return 类型时,你实际上并不需要 as const

// helper function to get a member of the `Label` union
type LabelByKey<K extends labelKey> = Extract<Label, {key: K}>

function createLabel(someCondition: true): LabelByKey<labelKey.RATING>;
function createLabel(someCondition: false): LabelByKey<labelKey.EPISODE_TITLE>;
function createLabel(someCondition: boolean): Label {
  if (someCondition) {
    return {
      key: labelKey.RATING,
      options: { rating: '5' },
    }
  }
  else {
    return {
      key: labelKey.EPISODE_TITLE,
      options: { episodeNumber: 1, seasonNumber: 2, episodeTitle: 'Hello'  },
    }
  }
}

现在,当您调用 createLabel(false) 时,您知道您得到的标签类型如下:

const label: {
    key: labelKey.EPISODE_TITLE;
    options: {
        episodeNumber: number;
        seasonNumber: number;
        episodeTitle: string;
    };
}

我们不需要那些额外的重载,因为现在我们可以将第二个重载与 key: labelKey.EPISODE_TITLE 相匹配。

Typescript Playground Link

解决方案 3:泛型

您需要将 labelKey 与其 options 匹配的信息已在 Label 联合中可用。所以你不需要在重载中再次输入它。我们可以使用通用函数而不是重载函数,并使用相同的 LabelByKey 辅助类型。

type LabelByKey<K extends labelKey> = Extract<Label, {key: K}>

const getLabel = <K extends labelKey>(
  key: K,
  options: LabelByKey<K>['options']
) => {
  return '';
};