Недавно в RxJS Primitives я столкнулся с ситуацией, когда один из методов — concat изначально был рассчитан на получение в качестве аргумента списка строк, а в методе использовались остальные (...args
) параметры, имитирующие сигнатуру и передав их в String .prototype.concat
Я создал Проект StackBlitz с кодом для каждого
шага, которому можно следовать.
Я хотел отрефакторить его для поддержки массива строк, но обнаружил, что в текущей реализации это невозможно и выдает ошибку TypeScript
:
export function concat(...args: string[]): MonoTypeOperatorFunction<string> { return (source: Observable<string>) => source.pipe(map(value => value.concat(...args))) }
fromString('Testing').pipe(concat([' one', ' two'])).subscribe(console.log)
Argument of type 'string[]' is not assignable to parameter of type 'string'.
Из-за того, как TypeScript обрабатывает остальные параметры, он ожидает список параметров в виде одной строки, которая превращается в аргументы, подобные массиву.
Чтобы обойти это, мы можем использовать TypeScript function
перегрузка.
Как перегрузить функции с остальными параметрами
Моя первая попытка написать этот метод привела к этому коду, который пытается сохранить информацию о типе во всех реализациях:
function concat(...args: string[]): MonoTypeOperatorFunction<string>; function concat(args: string[]): MonoTypeOperatorFunction<string>; function concat(...args: string[]): MonoTypeOperatorFunction<string> { return (source: Observable<string>) => source.pipe(map(value => value.concat(...args))) }
export { concat }
Это привело к ошибке во второй реализации:
function concat(args: string[]): MonoTypeOperatorFunction<string> (+1 overload)
This overload signature is not compatible with its implementation signature.
Похоже, что TypeScript не может преобразовать тип остатка Array в значение, подобное массиву arguments
, чтобы обойти это, нам нужно использовать значение any
в последней реализации:
function concat(...args: string[]): MonoTypeOperatorFunction<string>; function concat(args: string[]): MonoTypeOperatorFunction<string>; function concat(...args: any): MonoTypeOperatorFunction<string> { return (source: Observable<string>) => source.pipe(map(value => value.concat(...args))) }
export { concat }
Итак, теперь компилятор TypeScript перестал жаловаться, и мы можем протестировать его, используя оба поддерживаемых типа аргументов:
fromString('Testing').pipe(concat(' one', ' two')).subscribe(console.log) // Testing one two
fromString('Testing').pipe(concat([' one', ' two'])).subscribe(console.log) // Testing one, two
Однако, если вы посмотрите на вывод результата, вы заметите ошибку в нашей реализации Array, где в тексте появляется запятая — проблема в том, что теперь реализация обрабатывает массив как первый аргумент в списке arguments
— и изменение последнего метода на args:any
удалит разрушение нашего оставшегося параметра.
Чтобы решить эту проблему, нам нужно снова использовать тип any
, чтобы деструктурировать наши аргументы и проверить, является ли первый из них массивом, если это так, мы используем его в качестве наших деструктурированных аргументов в методе String.prototype.concat
, но если это строка, мы передаем все аргументы с использованием деструктуризации:
function concat(...args: string[]): MonoTypeOperatorFunction<string>; function concat(args: string[]): MonoTypeOperatorFunction<string>; function concat(...args: any): MonoTypeOperatorFunction<string> { const inArgs: any[] = [...args]; if (inArgs[0] instanceof Array) { return (source: Observable<string>) => source.pipe(map(value => value.concat(...inArgs[0]))) } return (source: Observable<string>) => source.pipe(map(value => value.concat(...inArgs))) }
export { concat }
Теперь реализация работает так, как задумано — наш метод может принимать один или несколько строковых аргументов или массив строк в качестве первого аргумента — и их невозможно перепутать — если вы попытаетесь добавить к аргументам второй массив, компилятор TypeScript будет искать однострочные аргументы.