Ограничение аргумента обратного вызова, передаваемого другой функции

Рассмотрим следующую функцию TypeScript:

function example<T>(
  ...[callback, batch]:
    | [callback: (param: T) => void, batch?: false]
    | [callback: (param: T[]) => void, batch: true]
) {
  if (batch) {
    callback([]);
  } else {
    callback({} as T);
  }
}

Определение типа callback указывает тип аргумента, поэтому можно было бы ожидать, что тип аргумента будет выведен в следующих вызовах:

example<string>((param) => {});        // Param should be inferred as string
example<string>((param) => {}, true);  // Param should be inferred as string[]

Однако TypeScript жалуется, что не может определить тип param:

error TS7006: Parameter 'param' implicitly has an 'any' type.

example<string>((param) => {});
                 ~~~~~

error TS7006: Parameter 'param' implicitly has an 'any' type.

example<string>((param) => {}, true);
                 ~~~~~

Предоставление явного типа решает проблему, но я считаю, что у TypeScript достаточно информации, чтобы сделать вывод самостоятельно. Фактически, TypeScript сообщает о неправильном вводе при предоставлении недопустимого явного типа:

error TS2345: Argument of type '[(param: number) => void]' is not assignable to parameter of type '[callback: (c: string) => void, batch?: false | undefined] | [callback: (c: string[]) => void, batch: true]'.
  Type '[(param: number) => void]' is not assignable to type '[callback: (c: string) => void, batch?: false | undefined]'.
    Type at position 0 in source is not compatible with type at position 0 in target.
      Type '(param: number) => void' is not assignable to type '(c: string) => void'.
        Types of parameters 'param' and 'c' are incompatible.
          Type 'string' is not assignable to type 'number'.

example<string>((param: number) => {});
                ~~~~~~~~~~~~~~~~~~~~~

error TS2345: Argument of type '[(param: number[]) => void, true]' is not assignable to parameter of type '[callback: (c: string) => void, batch?: false | undefined] | [callback: (c: string[]) => void, batch: true]'.
  Type '[(param: number[]) => void, true]' is not assignable to type '[callback: (c: string[]) => void, batch: true]'.
    Type at position 0 in source is not compatible with type at position 0 in target.
      Type '(param: number[]) => void' is not assignable to type '(c: string[]) => void'.
        Types of parameters 'param' and 'c' are incompatible.
          Type 'string[]' is not assignable to type 'number[]'.
            Type 'string' is not assignable to type 'number'.

example<string>((param: number[]) => {}, true);
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Есть ли способ заставить это работать без явного типа аргумента?

Вы нажали ms/TS#42987 ; вы хотите, чтобы это вело себя точно так же, как перегрузки со стороны вызывающего абонента. По этой ссылке на игровую площадку показана перегруженная версия, которую я, вероятно, и предложил бы в качестве обходного пути для звонящих. Это полностью отвечает на ваш вопрос? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 22.06.2024 19:48

@jcalz Это действительно та же проблема! Спасибо, что нашли время сообщить об этом в официальный репозиторий TypeScript 😄 Вероятно, перегрузки помогут. Кстати говоря, можете ли вы использовать их в функциональном члене объекта?

Carles Capellas 22.06.2024 22:28

Я напишу ответ, когда у меня будет возможность. Я не понимаю, что «можно ли использовать перегрузки в функциональном члене объекта?» значит точно. Вы определенно можете определять объекты с перегруженными сигнатурами вызовов методов, но реализация может быть сложной (см. ms/TS#47669)... однако это не мешает вам их писать. Похоже, это дополнительный вопрос, который не совсем подходит для раздела комментариев SO.

jcalz 22.06.2024 22:36
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
3
3
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

В настоящее время это нереализованная функция TypeScript, описанная по адресу microsoft/TypeScript#42987 . Функция, которая принимает rest параметр типом которого является объединение типов кортежей , ведет себя почти как перегруженная функция с несколькими сигнатурами вызовов. Действительно, если вы начнете называть это:

declare function example<T>(
    ...[callback, batch]:
        | [callback: (param: T) => void, batch?: false]
        | [callback: (param: T[]) => void, batch: true]
): void

Среды IDE с поддержкой IntelliSense покажут вам списки завершения, которые выглядят точно так же, как перегрузки:

example(⎀
//      ^ 1/2 example(callback: (param: unknown) => void, batch?: false | undefined): void
//      ^  2/2 example(callback: (param: unknown[]) => void, batch: true): void

К сожалению, как вы заметили, они ведут себя не совсем как перегрузки:

example<string>((param) => { });   // err
//               ~~~~~ implicit any
example<string>((param) => { }, true);  // err
//               ~~~~~ implicit any

Если вы перепишете эту функцию, чтобы она была настоящей перегрузкой:

declare function example<T>(callback: (p: T) => void, batch?: false): void;
declare function example<T>(callback: (p: T[]) => void, batch: true): void;

Тогда вы получите желаемое поведение:

example<string>((param) => { });  
//               ^? (parameter) param: string
example<string>((param) => { }, true);
//               ^? (parameter) param: string[]

Это прискорбно, поскольку объединения остальных параметров с типом кортежа могут быть намного компактнее, чем перегрузки с несколькими сигнатурами вызовов, и их легче генерировать программно. Но до тех пор, пока не будет реализован microsoft/TypeScript#42987, я бы предложил писать перегрузки, если вы хотите такое поведение для вызывающих абонентов.

Детская площадка, ссылка на код

Вы можете использовать перегрузку функций

function example<T>(callback: (param: T) => void, batch?: false): Array<unknown>
function example<T>(callback: (param: T[]) => void, batch: true): {}
function example() {
  const [callback, batch] = arguments;
  if (batch) {
    callback([]);
  } else {
    callback({});
  }
}

Это позволяет вам объявить несколько отдельных способов использования функции, не смешивая их объявления.

Спасибо за ваш вклад @ug_! Функции по-прежнему необходимо объявлять параметры, но перегрузка — это именно то решение, которое я искал!

Carles Capellas 23.06.2024 16:41

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

React Query – использовать ответ от выборки списка на setQueryData для каждого элемента?
Как установить новое значение для «Сигнала», который вызывается из «Эффекта»?
Как я могу вывести типы аргументов нескольких функций в TypeScript
Сопоставьте значения из NgForm с моделью машинописного текста, определенной в Angular
Могу ли я использовать файлы ts точно так же, как я использую файлы js?
Проблема вывода типа с массивом универсальных объектов конфигурации с разными внутренними типами, которые зависят от другого внутреннего свойства
Почему добавление круглых скобок удаляет «только для чтения [x: число]: число;» из этого типа?
Введите 'строка | логическое значение не может быть назначено для ввода «никогда» в машинописном тексте
Как добавить динамические метаданные на страницу [slug] в маршрутизаторе приложений next.js
Программно переключить свойство, нулевое или необязательное, на обязательное в Zod