Это ограничение дизайна TypeScript. Есть ряд проблем GitHub, поданных по этому поводу; microsoft / TypeScript # 25826 может быть хорошим, чтобы сосредоточиться на нем.
Когда вы вызываете Test1
, компилятор должен оба вывести параметр типа * generic c T
, а также контекстный тип для аргумента обратного вызова arg
. Он делает это за несколько «проходов», и, к сожалению, делает это в плохом порядке для вашего случая.
У меня нет полной картины того, что происходит конкретно, но набросок выглядит следующим образом. Когда вы вызываете,
Test1('test', (arg) => { arg.ZZ }, m => ({ ZZ: 123 }));
, компилятор хочет вывести тип arg
из T
и тип T
из arg
, и он не может этого сделать, поэтому это откладывает вывод на потом. Он переходит ко второму обратному вызову. Этот аргумент также имеет аннотированный аргумент, поэтому здесь он также откладывает вывод. При следующем прохождении он должен попытаться вывести параметр типа T
, но для него пока нет информации, которую можно использовать при проверке первого обратного вызова. Он должен что-то выбрать сейчас, поэтому он терпит неудачу и выбирает unknown
. И вы получите свою ошибку. 101
Возможно, что алгоритм вывода TypeScript должен быть лучше приспособлен к таким вещам, но это может быть огромным прорывом. Согласно комментарию в этой проблеме GitHub
После довольно продолжительного обсуждения вариантов у нас нет идей, как это исправить, не нарушая другие сценарии ios. Полное объединение - это, конечно, «решение», но это, по сути, полное переписывание, поэтому на данный момент не совсем в картах.
Упомянутое «полное объединение» - это топи c of microsoft / TypeScript # 30134 , но, похоже, нет никаких непосредственных планов что-либо здесь делать.
Итак, до тех пор, пока это не будет исправлено, есть обходные пути. Самый простой обходной путь - аннотировать параметр для второго обратного вызова:
Test1('test', arg => { arg.ZZ }, (m: any) => ({ ZZ: 123 })) // T is {ZZ: number}
Это откладывает вывод для arg => ...
, а затем не откладывает для (m: any) => ...
. Он знает типы и видит, что тип возвращаемого значения {ZZ: number}
. Теперь из этого можно правильно вывести T
, и на втором проходе, как известно, arg
имеет тип {ZZ: number}
и все хорошо.
Аналогично вы можете указывать типы чтобы помочь логическому выводу, либо аннотируя arg
, либо задав T
при вызове Test1
:
Test1('test', (arg: { ZZ: number }) => { arg.ZZ }, m => ({ ZZ: 123 })) // T is {ZZ: number}
Test1<{ ZZ: number }>('test', arg => { arg.ZZ }, m => ({ ZZ: 123 })) // obvs
Другой подход заключается в переключении порядка ваших обратных вызовов, поскольку логический вывод имеет тенденцию к действуйте слева направо:
declare function Test2<T>(msg: string, fnTake: (arg: any) => T, fn: (arg: T) => void): void;
Test2('test', m => ({ ZZ: 123 }), arg => { arg.ZZ }); // T is {ZZ: number}
Я думаю, что такая же отсрочка вывода происходит на первом проходе, но теперь на втором проходе компилятор может проверить m => ...
и использовать контекстный тип any
для m
, а затем выводим тип возврата {ZZ: number}
, и теперь у него есть кандидат на вывод для T
, и все работает, когда доходит до обратного вызова arg => ...
.
Хорошо Надеюсь, что это имеет смысл и дает некоторые направления. Удачи!
Детская площадка ссылка на код