Можно ли сузить типы перегруженных параметров без исчерпывающей проверки каждого параметра в теле функции? - PullRequest
2 голосов
/ 15 января 2020

Я бы хотел определить функцию, которая может принимать параметры, набранные одним из двух способов. Например:

type Fn = {
    (abc: number, def: string): void,
    (abc: string): void,
};

С учетом сигнатуры этого типа, если abc является числом, то def является строкой, а если abc является строкой, то def не определено. Это понятно людям, но может ли Typescript это распознать? Например, следующая реализация завершается ошибкой:

const fn: Fn = (abc: number | string, def?: string) => {
    if (typeof abc === 'string') console.log(abc.includes('substr'));
    else console.log(def.includes('substr'));
}

, поскольку, хотя тип abc был сужен, TS не понимает, что тип def также был определен, поэтому def.includes не допускается Группировка типов аргументов распознается для вызывающих абонентов функции, поэтому следующее, как и ожидалось, запрещено:

fn('abc', 'def');

Но перегруженная группировка типов, похоже, не действует внутри функции .

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

Другая проблема c избыточности заключается в том, что каждый возможный тип аргумента должен быть указан не только в type, но и в параметре функции. список. Например, (abc: number) и (abc: string) в определении типа также требует = (abc: number | string) в списке параметров.

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

  • Передайте объект типа { abc: number, def: string } | { abc: string } вместо нескольких отдельных параметров, затем передайте объект go через защиту типа

  • Используйте две отдельные функции для двух разных типов параметров

Но я бы предпочел использовать перегрузку если есть достойный способ справиться с этим.

1 Ответ

0 голосов
/ 15 января 2020

Все ваши подходы могут быть разумными: (1) отдельные объявления функций (2) объединение параметров объекта или (3) перегрузка функции, как в вопросе.

Я бы предпочел (1), если вызывающая программа уже имеет достаточно информации, чтобы решить, какую функцию следует вызывать, поскольку это уменьшает общую условную сложность fn body.

(2) имеет больше смысла с распознаваемым типом объединения, так что вы не потеряете лишние проверки свойств для вызывающей стороны. Пример :

type OverloadParam =
    | { kind: "a", abc: number; def: string }
    | { kind: "b"; abc: string }

type Fn = (arg: OverloadParam) => void

const fn: Fn = (args) => {
    if (args.kind === "a") {
        args.def.includes('substr')
    } else {
        args.abc.includes('substr')
    }
}

Кроме того, вам не нужно перечислять типы дважды как в Fn, так и в составе fn подписи. Я нашел это old ie здесь как причину: только функции с одной перегрузкой могут применять контекстный тип.

С (3) не существует умного способа обработки переменных параметров функции внутри функции. TS / JS не поддерживают реализацию перегрузки функций, например:

function fn(abc: number, def: string): void { }
function fn(abc: string): void { } 
// error (TS): Duplicate function implementation. JS would overwrite the first declaration

Поэтому вам всегда придется использовать более широкую сигнатуру типа с необязательными параметрами и / или типами объединения и сужать эти типы внутри. тело функции.

...