TypeScript重载实现与签名不匹配

时间:2019-03-21 12:00:33

标签: typescript

我想使用TypeScript的重载功能来创建一个函数,该函数根据传入的参数返回不同的类型。我设法使其正常工作,但是编译器无法在重载函数的实现中捕获错误。

下面的示例是the TypeScript documentation中的示例(请参见下文)。该函数的参数接受两种不同的类型:

  • object:期望返回类型number
  • number:期望返回类型object

// With incompatible types

const suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x): any {
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    // This part does not match the overload definition. The signature
    // expect a `number` but we provide a `string`. The compiler does
    // not throw an error in that case.
    return pickedCard.toString();
  } else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

const card = pickCard([{ suit: 'hearts', card: 5 }])

card.toFixed() // throw a runtime error: card.toFixed is not a function

// With compatible types

type Hand = { suit: string; card: number };

type HandWithScore = Hand & { score: number }

const suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: Hand[]): HandWithScore;
function pickCard(x: number): Hand;
function pickCard(x): HandWithScore | Hand {
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    // This part does not match the overload definition.
    return { suit: 'hearts', card: x % 13 };
  } else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

const card = pickCard([{ suit: 'hearts', card: 5 }])

card.score.toFixed() // throw a runtime

在这种情况下,实现与重载的定义不匹配,并且编译器也未警告我们,这意味着我们可能会在运行时遇到问题,因为我们期望number,但实际上却得到了{{ 1}}。是否期望编译器不抛出?

您可以在the TypeScript playground内测试样本。

1 个答案:

答案 0 :(得分:1)

TypeScript编译器无法分析代码的逻辑以确保其符合合同规定。我很惊讶地发现它无法从您应该从数字或对象返回的函数中返回字符串的事实中发现,但是能够做到这一点很可能就在“待办事项”列表中(有很多要做),但还没有到那儿。我倾向于认为它的优先级会很低,因为它无论如何也无法确保正确性,因为它无法分析逻辑流程。

如果您为卡定义类型,则可以为实现指定number | Card而不是any

const suits = ["hearts", "spades", "clubs", "diamonds"];
type Card = { suit: string; card: number; };

function pickCard(x: Card[]): number;
function pickCard(x: number): Card;
function pickCard(x): number | Card {
  if (typeof x == "object") {
    const cards = x as Card[];
    let pickedCard = Math.floor(Math.random() * cards.length);
    return pickedCard.toString();  // <========================== Compilation error
  } else if (typeof x == "number") {
    const n = x as number;
    let pickedSuit = Math.floor(n / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

const card = pickCard([{ suit: 'hearts', card: 5 }])

card.toFixed()

On the playground

在分支不正确的情况下,这不能为您提供帮助,但是在这种情况下,当您从应该返回数字或卡的函数中返回字符串时,它可以为您提供帮助。

(也许您可以在不定义类型的情况下使它起作用,但是我不能立即这样做,在任何情况下,所有重新键入似乎都是有问题的。)

如果实现是不平凡的,您甚至可以将其与我先前的建议分开,以将实现拆分为自己的类型安全函数:

const suits = ["hearts", "spades", "clubs", "diamonds"];
type Card = { suit: string; card: number; };

function pickCard(x: Card[]): number;
function pickCard(x: number): Card;
function pickCard(x): number | Card {
  if (typeof x == "object") {
    return pickCard_Cards(x as Card[]);
  } else if (typeof x == "number") {
    return pickCard_number(x as number);
  }
}
// These wouldn't be exported
function pickCard_Cards(cards: Card[]): number {
    let pickedCard = Math.floor(Math.random() * cards.length);
    return pickedCard.toString();  // <========================== Compilation error
}
function pickCard_number(n: number): Card {
    let pickedSuit = Math.floor(n / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
}

const card = pickCard([{ suit: 'hearts', card: 5 }])

card.toFixed()

On the playground

...以便每个分支的逻辑都得到单独检查(您不会意外地从应该返回Card[]的分支中返回number,反之亦然)。