Перегрузка функции Typescript на основе входных аргументов - PullRequest
0 голосов
/ 03 января 2019

Я ищу способ переопределить или перегрузить определенную группу функций.

Вот пример

const operationA = ({a, b, c}) => 'alpha';
const operationB = ({a, b }) => 'beta';
const operationC = ({a}) => 'gamma';

const operation = compose([operationA, operationB, operationC]);

operation({a}) // 'gamma'
operation({a, b}) // 'beta'
operation({a, b, c}) // 'alpha'

Есть ли способ сделать эту функцию в машинописи?

Ответы [ 2 ]

0 голосов
/ 03 января 2019

(далее я использую TypeScript 3.2)

Основная проблема вашего вопроса, если я его понимаю, заключается в трудности выбора правильной перегрузки во время выполнения. не является одной из целей TypeScript (см. Не цель № 5) для компиляции информации о типах из TypeScript в JavaScript.Система типов, добавленная TypeScript, полностью стирается во время выполнения.Итак, если вы хотите написать compose() для получения списка функций, вам нужно как-то проверить эти функции во время выполнения, чтобы определить, какую из них следует вызывать для определенного аргумента.Эта функциональность действительно не существует в JavaScript, хотя.Ну, вы можете использовать свойство length функции, чтобы увидеть, сколько много аргументов она ожидает, но в приведенных вами примерах каждая функция принимает ровно один аргумент.Поэтому мы не можем использовать этот подход здесь.

Один из возможных путей продвижения вперед - добавить свойство к каждой функции.Это свойство будет методом, который принимает потенциальный набор аргументов и возвращает true, если эти аргументы действительны для функции, и false, если они не являются.По сути, вы вручную добавляете необходимую способность проверки, которая отсутствует в языке.

Если мы сделаем это, мы можем compose() принять список таких «функций проверки аргументов», например:

type ArgValidatingFunction =
  ((...args: any[]) => any) & { validArgs(...args: any): boolean };

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends
  ((k: infer I) => void) ? I : never;

function compose<F extends ArgValidatingFunction[]>(...fn: F): UnionToIntersection<F[number]>;
function compose(...fn: ArgValidatingFunction[]): Function {
  return Object.assign(
    (...args: any[]) => (fn.find(f => f.validArgs(...args))!(...args)),
    { validArgs: (...args: any[]) => fn.some(f => f.validArgs(...args)) }
  );
}

Подпись типа для compose принимает список аргументов ArgValidatingFunction и возвращает пересечение его элементов .TypeScript представляет перегрузки как зависящее от порядка пересечение подписей.Я не могу на 100% гарантировать, что компилятор выдаст тот же порядок перегрузки, что и переданные функции, но, похоже, он работает в моем тестировании.

Реализация compose использует метод ArgValidatingFunction 'validArgs и выполняет find() для переданных функций, чтобы выбрать правильную функцию.Я также реализую метод validArgs() в возвращаемой функции, чтобы возвращаемое значение compose() также было ArgValidatingFunction (что хорошо, потому что подпись типа утверждает, что это так).

Теперь мы можемпопробуйте использовать его, но это не тривиально ... мы должны добавить эти методы:

const operationA = ({ a, b, c }: { a: any, b: any, c: any }): 'alpha' => 'alpha';
operationA.validArgs = (...args: any[]) => 
  (args.length === 1) && ('a' in args[0]) && ('b' in args[0]) && ('c' in args[0]);

const operationB = ({ a, b }: { a: any, b: any }): 'beta' => 'beta';
operationB.validArgs = (...args: any[]) => 
  (args.length === 1) && ('a' in args[0]) && ('b' in args[0]);

const operationC = ({ a }: { a: any }): 'gamma' => 'gamma';
operationC.validArgs = (...args: any[]) => 
  (args.length === 1) && ('a' in args[0]);

Вот так:

const operation = compose(operationA, operationB, operationC);

const beta = operation({ a: 3, b: 3 }); // "beta" at compile time;
console.log(beta); // "beta" at runtime

Похоже, что это работает как во время компиляции, так ивремя выполнения.


Так что это один из способов.Это не легко и не красиво, но, возможно, это работает для вашего (или чьего-либо) варианта использования.Надеюсь, это поможет.Удачи!

0 голосов
/ 03 января 2019

Подход, который, возможно, вы уже оценили, заключается в использовании Interface в качестве входных данных для вашего основного operation метода, а затем отправке правильных вспомогательных методов в зависимости от ввода.

Что-то вроде:

interface OperationArgs {
 a: string;
 b?: string;
 c?: string;
}

Итак, первое значение является обязательным, два других являются необязательными.

Внутри вашего operation метода вы можете сделать что-то вроде:

public operation(inp: OperationArgs) {
  if (inp.c) {
    return this.operationC(inp);
  }
  if (inp.b) {
    return this.operationB(inp);
  }
  return this.operationA(inp);
}

Другой подход заключается в использовании Proxy , но они еще не полностью поддерживаются в JS (проводник отсутствует).Вы можете создать класс, который возвращает экземпляр Proxy, и перехватить методы operation с помощью метода get обработчика.В зависимости от заданного реквизита, вы фактически вызовете правильный метод в экземпляре.

...