Давайте посмотрим, что я бы рассмотрел суть этой проблемы:
declare function foo<T>(x: { prop: T }): void;
declare function foo<T>(x: { prop: T, func: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // error!
// ┌─────────────────> ~
// Parameter 'x' implicitly has an 'any' type.
Ситуация такая же, как и в этой сообщенной проблеме , помеченной как "работающая по назначению",Проблема в том, что вызов соответствует первой сигнатуре перегрузки, и поэтому компилятор не может понять, как определить тип x
.
Как совпадают обе подписи? Ответ в том, что типы объектов в TypeScript не точны . Если у вас есть интерфейс, такой как interface Foo {a: string}
, и интерфейс interface Bar extends Foo {b: string}
, то это факт, что каждый экземпляр Bar
также является Foo
. Это означает, что в общем случае тип, подобный {a: string}
, совместим с любым объектом с дополнительными свойствами, если он имеет строковое свойство с именем a
. Часто компилятор пытается помочь людям не совершать ошибок, предупреждая о лишних свойствах , но эти проверки здесь не выполняются, поскольку func
относится к одному из типов, которые он проверяет. Не знаю почему, и, возможно, это ограничение дизайна или ошибка.
В любом случае, компилятор видит func
как некоторый тип функции, но, поскольку он соответствует первой перегрузке, контекстная типизация для x
не делает 'Это сработает, и вы получите эту «неявную любую» ошибку.
Есть несколько способов, чтобы продолжить здесь. Одним из них является переключение порядка перегрузок таким образом, чтобы первая перегрузка была более строгой. Сигнатурная перегрузочная подпись сверху предотвращает прохождение разрешения перегрузки. В общем, это хорошее правило: более конкретные перегрузки вверху, более общие перегрузки внизу:
declare function foo<T>(x: { prop: T, func: (x: T) => void }): void;
declare function foo<T>(x: { prop: T }): void;
foo({ prop: "hey", func: x => x.length }); // okay
, который выбирает первую перегрузку, и тип func
известен, а x
выводится как string
.
Другой способ продолжить - взять более общую перегрузку и изменить ее так, чтобы она фактически запрещала рассматриваемый вызов, делая func
свойством, которое оно можетЭто не так:
declare function foo<T>(x: { prop: T, func?: never }): void;
declare function foo<T>(x: { prop: T, func: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // okay
Это работает сейчас, потому что при первой перегрузке func
является необязательным свойством, значение которого имеет тип never
. Нет другого пути, кроме undefined
, чтобы удовлетворить это, а такая функция, как x => x.length
, конечно, нет. Таким образом, вызов пропускает первую перегрузку, выбирает вторую и выводит string
для x
.
Наконец, в случае, когда две рассматриваемые перегрузки так похожи, за исключениемВ данном случае я был бы склонен свести их в одну подпись и полностью забыть о перегрузках. Это может не соответствовать вашему варианту использования, но об этом следует помнить:
declare function foo<T>(x: { prop: T, func?: (x: T) => void }): void;
foo({ prop: "hey", func: x => x.length }); // okay
Теперь существует только одна подпись вызова, и func
может присутствовать или отсутствовать.
Один из них, надеюсь, будет работать на вас. Я протестировал второй на вашем примере , для чего он стоит.
Хорошо, удачи!
Ссылка на код