Реализация перегрузки TypeScript не соответствует подписи

#typescript

#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 amp; { 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 playground.

Комментарии:

1. Да, это ожидаемо. Вы сообщили компилятору, что возвращаемый тип был any . Используйте number | { suit: string; card: number; } , и это позволит вернуть только это. То же самое для вашего x аргумента. И напишите модульные тесты.

2. @JBNizet Да, вы правы, но это устраняет проблему с этим примером, поскольку типы полностью несовместимы. Представьте себе другой вариант использования с объединением типов для двух типов, которые разделяют некоторые свойства. Мы снова столкнулись с проблемой.

3. Компилятор не может проверять каждую ошибку, которую вы можете допустить в своем коде. Пишите тесты. Сделайте обзоры кода.

4. Да, конечно, но это тоже цель компилятора.

5. Я обновил примеры несовместимыми типами.

Ответ №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()
  

<a rel="noreferrer noopener nofollow" href="https://www.typescriptlang.org/play/index.html#src=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(); // На игровой площадке

…так что логика каждой ветви проверяется индивидуально (вы случайно не возвращаете a Card[] из ветви, которая должна возвращать a number , и наоборот).

Комментарии:

1. Спасибо за ответ! Дело в том, что если возвращаемый тип частной функции неверен, мы получаем ту же проблему. Мы переносим проблему в другое место.

2. @SamuelVaillant — Да, но, по крайней мере, это помогает вам уловить это, если реализация нетривиальна. Но хуже того, есть ответ получше (но он хорошо сочетается с моим оригинальным подходом). Обновлено. 🙂