网站搜索

TypeScript 重载实现与签名不匹配


我想使用 TypeScript 的重载功能来创建一个根据传入参数返回不同类型的函数。我设法让它工作了,但编译器无法捕捉重载函数实现内部的错误。

下面的例子来自 TypeScript 文档(见下文)。该函数接受两种不同类型的参数:

  • 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,但实际上得到的是一个 string。编译器不报错这是预期的吗?

你可以在 TypeScript 游乐场中测试示例。

答案#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()

在操场上。

这不能帮你解决没有正确分支的情况,但它可以帮你处理从应该返回数字或卡片的函数返回字符串的这种情况。

(也许你可以在不定义类型的情况下让它工作,但我一时没有做到,而且无论如何,所有这些重复输入似乎都有问题。)

如果实现并非简单,你甚至可以将其与我之前建议的将实现拆分到各自类型安全函数的方法结合起来:

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()

在操场上

...这样每个分支的逻辑都会被单独检查(你不会不小心从应该返回 number 的分支返回 Card[],反之亦然)。