Как исправить общий класс Typescript, когда «существуют два разных типа с этим именем, но они не связаны»

Рассмотрим следующий код. ColumnSorter предназначен для инкапсуляции набора функций сортировки по их строковым именам, а затем применения правильной функции сортировки к массиву.

export class ColumnSorter<T> {
    constructor(private map: Map<string, (a: T, b: T) => number>) {
    }
    public sortBy<T>(sortcol: string, items: T[]): void {
        // *** DOES NOT COMPILE:
        items.sort(this.map.get(sortcol));
        // *** DOES NOT COMPILE EITHER:
        let fn: (a: T, b: T) => number = this.map.get(sortcol);
    }
}

// here is an example Quote arrays. Others could be position, account, etc.
// implementing with sort functions for that type.
const quoteFunctionMap: Map<string, (a: Quote, b: Quote) => number> = new Map([
    ['byLastPrice', (q1: Quote, q2: Quote) => {
        return q1.lastPrice - q2.lastPrice;
    }],
    ['bySymbol', (q1: Quote, q2: Quote) => {
        return q1.symbol.localeCompare(q2.symbol);
    }]
]);

let colsort = new ColumnSorter<Quote>(quoteFunctionMap);
let quotes: Quote[] = [new Quote(), new Quote()]; // blank quotes, for compile test
colsort.sortBy("byLastPrice", quotes);

Проблема заключается в извлечении функции сортировки из карты. Сообщение об ошибке подробное, но мне недостаточно, чтобы найти корень проблемы. Чего-то не хватает в моем коде?

Argument of type '((a: T, b: T) => number) | undefined' is not assignable to parameter of type '((a: T, b: T) => number) | undefined'.
  Type '(a: T, b: T) => number' is not assignable to type '(a: T, b: T) => number'. Two different types with this name exist, but they are unrelated.
    Types of parameters 'a' and 'a' are incompatible.
      Type 'T' is not assignable to type 'T'. Two different types with this name exist, but they are unrelated.
        'T' could be instantiated with an arbitrary type which could be unrelated to 'T'.ts(2345)

Что такое класс Quote (нам нужны определения)?

zenly 18.04.2023 15:49

@pink просто считайте, что это простой объект данных, где символ — это строка, а lastPrice — это число.

John Churchill 18.04.2023 20:24
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
2
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш метод ColumnSorter.sortBy должен использовать общий T из класса, а не определять свой собственный.

export class ColumnSorter<T> {
  constructor(private map: Map<string, (a: T, b: T) => number>) {
  }
  // NOTE: changed from sortBy<T> to just sortBy below!
  public sortBy(sortcol: string, items: T[]): void {
    items.sort(this.map.get(sortcol));
    let fn = this.map.get(sortcol);
    if (!fn) {
      throw new Error(`No sorter exists for ${sortcol}`);
    }
    // ...
  }
} 

Спасибо, я думал это просто! Но для моего полного понимания, почему вторая строка (пусть fn...) все еще не компилируется? Используя ваше определение класса, я получаю Type '((a: T, b: T) => number) | undefined' is not assignable to type '(a: T, b: T) => number'.⏎ Type 'undefined' is not assignable to type '(a: T, b: T) => number'.

John Churchill 18.04.2023 16:11

Это потому, что map.get может возвращать значение undefined. Поэтому вам нужно будет выдать ошибку, если функция не может быть найдена с помощью sortcol на карте или что-то в этом роде.

cdimitroulas 18.04.2023 17:42

Обновлен мой ответ, чтобы включить эту логику.

cdimitroulas 18.04.2023 17:43

Да, карта может возвращать значение undefined, но я включил это в ссылочный тип. Вы можете удалить тип и заставить его компилироваться (что вы и сделали, проверка экземпляра не требуется), но теперь это не тот же тип, а просто ссылка на любой тип. Я поэкспериментировал с этим и обнаружил, что проблема в том, что number | undefined интерпретируется компилятором как возвращаемый тип функции, а не как альтернативный возвращаемый тип. Заключив его в круглые скобки, он компилируется: let fn: ((a: T, b: T) => number) | undefined

John Churchill 18.04.2023 21:01

Аннотация типа на самом деле не нужна, Typescript уже правильно выводит ее как ((a: T, b: T) => number) | undefined — вы можете увидеть это, наведя курсор на fn здесь -> tsplay.dev/m3BLkN

cdimitroulas 19.04.2023 14:01

Другие вопросы по теме