39 votes

Comment annoter une fonction avec plusieurs signatures d'appel possibles dans Flow?

En JavaScript, il est courant d'avoir une fonction qui peut être appelée à plus d'un titre – par exemple, avec quelques arguments de position ou un seul objet d'options ou une combinaison des deux.

J'ai essayé de travailler sur la façon d'annoter cette.

Une façon que j'ai essayé était pour annoter reste args comme une union de différentes possibles des n-uplets:

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);

L'extrait ci-dessus est artificiel; je pourrais probablement juste utiliser (...args: Array<string | number>) dans cet exemple, mais pour les plus complexes des signatures (par exemple, impliquant un typée objet d'options qui peuvent être seuls ou avec l'accord préalable args) il serait utile de pouvoir définir un état précis, ensemble fini d'appel possible de signatures.

Mais le ci-dessus n'est pas de type case. Vous pouvez voir un tas de confusion des erreurs dans tryflow.

J'ai aussi essayé en tapant la fonction elle-même, en tant qu'union de séparer intégralité de la fonction defs, mais qui n'a pas de travail , soit:

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);

Comment devrais-je m'approche de type annotation des fonctions avec plusieurs d'appel possible de signatures? (Ou multi-signatures considéré comme un anti-modèle en Flux, et je ne devrais pas faire ça à tous – en ce cas, qu'est-ce que l'approche recommandée pour l'interaction avec des bibliothèques tierces que faire?)

9voto

EugeneZ Points 1434

Les erreurs que vous voyez sont en fait une combinaison d'un bug dans votre code et un bug dans le Flux.

Bug dans votre code

Nous allons commencer par la fixation de votre bug. Dans la troisième instruction else, vous affectez la valeur faux pour

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

La modification de la matrice de l'accès à l'index correct supprime deux erreurs. Je pense que nous pouvons tous les deux d'accord c'est exactement ce que l'Écoulement est pour, à trouver les erreurs dans votre code.

Rétrécissement de type

Afin d'obtenir à la cause racine du problème, nous pouvons être plus explicite dans la zone où les erreurs sont de sorte que l'on peut le plus facilement voir quel est le problème:

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

C'est effectivement la même qu'avant, mais parce que nous sommes très clairs sur ce qu' args[0] et args[1] devrait être à ce point. Ce qui nous laisse avec une seule erreur.

Bug dans le Flux

Le reste de l'erreur est du à un bug dans le Flux: https://github.com/facebook/flow/issues/3564

bug: tuple de type n'est pas d'interagir avec la longueur assertions.longueur >= 2 et [] | [nombre] | [numéro, numéro] type)

Comment taper les fonctions surchargées

Le flux n'est pas une grande affaire à des variadics avec différents types, comme dans ce cas. Variadics sont plus pour des trucs comme function sum(...args: Array<number>) où tous les types sont les mêmes et il n'y a pas de maximum arité.

Au lieu de cela, vous devriez être plus explicite avec vos arguments, comme suit:

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);

Cela provoque pas d'erreurs et je pense que c'est simplement plus facile à lire pour les développeurs, trop.

Une meilleure façon

Dans une autre réponse, Pavlo fourni une autre solution que j'aime plus que ma propre.

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

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

Il permet de résoudre les mêmes problèmes dans une beaucoup plus propre manière, ce qui permet beaucoup plus de souplesse. Par la création d'une intersection de plusieurs types de fonction, vous décrire chacune différente de l'appel de votre fonction, permettant de Flux pour essayer de chacun basé sur la façon dont la fonction est appelée.

8voto

Pavlo Points 7084

Vous pouvez définir plusieurs signatures de fonction en les joignant avec & :

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

L'essayer

1voto

Wilco Bakker Points 300

Parmi les trois entraînements possibles que vous avez donnés, j'ai compris comment le faire fonctionner avec un seul objet d'options. Toutefois, comme vous devez définir au moins un objet, vous devez définir chaque possibilité.

Comme ça:

 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({});
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X