Подтвердить что ты не робот

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

В JavaScript обычно используется функция, которая может быть вызвана более чем одним способом - например. с несколькими позиционными аргументами или один объект опций или некоторая комбинация из двух.

Я пытаюсь разобраться, как комментировать это.

Один из способов, которым я пытался, заключался в том, чтобы аннотировать остальные args как объединение различных возможных кортежей:

type Arguments =
  | [string]
  | [number]
  | [string, number]
;

const foo = (...args: Arguments) => {
  let name: string;
  let age: number;

  // unpack args...
  if (args.length > 1) {
    name = args[0];
    age = args[1];
  } else if (typeof args[0] === 'string') {
    name = args[0];
    age = 0;
  } else {
    name = 'someone';
    age = args[1];
  }

  console.log(`${name} is ${age}`);
};

// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);

Вышеприведенный фрагмент изобретателен; Я мог бы, вероятно, просто использовать (...args: Array<string | number>) в этом примере, но для более сложных сигнатур (например, с участием объекта типизированных опций, который может быть один или с предшествующими аргументами) было бы полезно иметь возможность определить точный конечный набор возможных вызовов подписи.

Но вышесказанное не проверяет тип. Вы можете увидеть путаницу ошибок в tryflow.

Я также попытался напечатать эту функцию как объединение отдельных целых функций defs, но тоже не работал:

type FooFunction =
  | (string) => void
  | (number) => void
  | (string, number) => void
;

const foo: FooFunction = (...args) => {
  let name: string;
  let age: number;

  // unpack args...
  if (args.length > 1) {
    name = args[0];
    age = args[1];
  } else if (typeof args[0] === 'string') {
    name = args[0];
    age = 0;
  } else {
    name = 'someone';
    age = args[1];
  }

  console.log(`${name} is ${age}`);
};

// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);

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

4b9b3361

Ответ 1

Ошибки, которые вы видите, представляют собой комбинацию ошибок в вашем коде и ошибку в потоке.

Ошибка в коде

Давайте начнем с исправления вашей ошибки. В третьем заявлении else вы присваиваете неправильное значение

  } else {
    name = 'someone';
    age = args[1]; // <-- Should be index 0
  }

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

Сужение типа

Чтобы добраться до основной причины проблемы, мы можем быть более явными в области ошибок, чтобы мы могли легче понять, в чем проблема:

if (args.length > 1) {
  const args_tuple: [string, number] = args;
  name = args_tuple[0];
  age = args_tuple[1];
} else if (typeof args[0] === 'string') {

Это фактически то же самое, что и раньше, но потому что мы очень четко знаем, что должно быть на данный момент args[0] и args[1]. Это оставляет нам одну ошибку.

Ошибка в потоке

Остальная ошибка - ошибка в потоке: https://github.com/facebook/flow/issues/3564

: тип tuple не взаимодействует с утверждениями длины (.length >= 2 и [] | [number] | [number, number] type)

Как напечатать перегруженные функции

Поток не очень хорош при работе с вариациями с разными типами, как в этом случае. Вариадики больше подходят для таких вещей, как function sum(...args: Array<number>), где все типы одинаковы и нет максимальной арности.

Вместо этого вы должны быть более явным с вашими аргументами, например:

const foo = (name: string | number, age?: number) => {
  let real_name: string = 'someone';
  let real_age: number = 0;

  // unpack args...
  if (typeof name === 'number') {
    real_age = name;
  } else {
    real_name = name;
    real_age = age || 0;
  }

  console.log(`${real_name} is ${real_age}`);
};

// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);

Это не вызывает ошибок, и я думаю, что читать их также проще для разработчиков.

Лучший способ

В другом ответе Pavlo предоставил другое решение, которое мне больше нравится, чем мое.

type Foo =
  & ((string | number) => void)
  & ((string, number) => void)

const foo: Foo = (name, age) => {...};

Он решает одни и те же проблемы гораздо более чистым способом, позволяя вам гораздо большую гибкость. Создавая пересечение нескольких типов функций, вы описываете каждый другой способ вызова своей функции, позволяя Flow попробовать каждый из них на основе того, как вызывается функция.

Ответ 2

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

Вот так:

type Arguments = 
    {|
        +name?: string,
        +age?: number
    |} |
    {|
        +name: string,
        +age?: number
    |} |
    {|
        +name?: string,
        +age: number
    |};

const foo = (args: Arguments) => {
  let name: string = args.name ? args.name : 'someone';
  let age: number = typeof args.age === 'number' && !isNaN(args.age) ? args.age : 0;
  console.log(`${name} is ${age}`);
}

// any of these call signatures are OK:
foo({ name: 'fred' });
foo({ name: 'fred', age: 30 });
foo({ age: 30 });

// fails
foo({});

Ответ 3

Вы можете определить несколько сигнатур функций, присоединив их к &:

type Foo =
  & ((string | number) => void)
  & ((string, number) => void)

Попробуйте.