Typescript: реализация универсального интерфейса

Рассмотрим следующий общий интерфейс:

interface Extractor<T> {
  extractCandidate(): T;
  process(candidate: T): T;
}

Концептуально каждая реализация Extractor отвечает за извлечение объектов определенного типа из некоторого источника данных. Потребитель Extractor может извлечь объект-кандидат, используя extractCandidate(), или, при наличии любого кандидата, выполнить над ним некоторую дальнейшую обработку, чтобы получить более усовершенствованную версию объекта, используя process().

Теперь предположим, что я реализую Extractor для класса MyClass, например:

class MyClass {
  a: string;
  b: string;
}

class MyExtractor implements Extractor<MyClass> {
  extractCandidate() {
    //Some dummy logic
    return new MyClass();
  }
  process(candidate) {
    //Some dummy logic
    let res = new MyClass();
    res.a = candidate.a;
    return res;
  }
}

Теперь предположим, что я создаю экземпляр MyExtractor и использую его:

let myExtractor = new MyExtractor();
let processed = myExtractor.process('blah');

Вопрос 1. Почему это не приводит к ошибке времени компиляции? Основываясь на определении интерфейса Extractor, я ожидаю, что компилятор не позволит мне вызывать myExtractor.process() ни с чем, кроме экземпляра MyClass, или, по крайней мере, с чем-то структурно совместимым.

Вопрос 2. Как обеспечить желаемое поведение? Мне просто нужно утверждать, что параметр candidate для MyExtractor.process() имеет тип MyClass?

Я подозреваю, что это как-то связано со структурной системой типизации TypeScript, но после прочтения некоторые связанные вопросы и Часто задаваемые вопросы, я до сих пор не уверен, как это конкретно применимо здесь.

Моя версия Typescript 2.1.4.


person ethan.roday    schedule 05.04.2017    source источник


Ответы (1)


Код, который вы разместили для MyExtractor, имеет следующую подпись для метода process:

process(candidate: any): MyClass

Причина этого в том, что вы не указали тип для candidate, поэтому по умолчанию это any.
Компилятор не будет жаловаться, потому что он удовлетворяет candidate: T (поскольку any может быть T).

Если вы измените свой код на:

process(candidate: MyClass) {
    ...
}

Тогда для:

let processed = myExtractor.process('blah');

Ты получишь:

Аргумент типа «blah» не может быть назначен параметру типа «MyClass».

Вы можете избежать этого, используя флаг --noImplicitAny, который вызовет компилятор, на который можно пожаловаться:

process(candidate) {
    ...
}

Говоря:

Параметр «кандидат» неявно имеет тип «любой».


Редактировать

Кандидату не разрешено быть «чем-то еще», ему разрешено быть any (и это значение по умолчанию), веская причина для этого, например, для перегрузки:

process(candidate: string): MyClass;
process(candidate: MyClass): MyClass;
process(candidate: any) {
    ...
}
person Nitzan Tomer    schedule 05.04.2017
comment
Хорошо, но разве я не указал тип ограничения для candidate? В MyExtractor T — это MyClass, так почему же candidate может быть чем угодно? В самом деле, если я утверждаю, что тип candidate равен string, и возвращаю пустую строку, я получаю ожидаемую ошибку: Тип '(кандидат: строка) => строка' не может быть присвоен типу '(кандидат: MyClass) => MyClass '. Типы параметров «кандидат» и «кандидат» несовместимы. Тип «MyClass» не может быть назначен типу «строка». - person ethan.roday; 05.04.2017
comment
Проверьте мой исправленный ответ - person Nitzan Tomer; 05.04.2017
comment
Было бы неплохо, если бы в этом случае TS мог контекстно набирать от process(candidate) до process(candidate: MyClass): MyClass. Я столкнулся с похожей путаницей с тем, как определение компонента React указывает тип для членов, но вы можете реализовать с неправильными типами, но вам все равно нужно указать правильный тип в вашей реализации. - person Aaron Beall; 05.04.2017
comment
@Aaron Тот факт, что интерфейс объявляет, что метод ожидает параметр MyClass, не обязательно означает, что реализация не может обрабатывать другие вещи, поэтому компилятор не будет делать такое предположение. Чтобы избежать этой проблемы, просто используйте noImplicitAny, тогда вы будете знать, когда/где у вас есть такие случаи. - person Nitzan Tomer; 05.04.2017