web-dev-qa-db-ja.com

Flowで複数の可能なコールシグネチャで関数に注釈を付ける方法は?

JavaScriptでは、複数の方法で呼び出される可能性のある関数を使用するのが一般的です。少数の位置引数またはを1つのオプションオブジェクトまたはと組み合わせて二つ。

私はこれに注釈を付ける方法を考え出そうとしてきました。

私が試みた1つの方法は、残りの引数にさまざまな可能なタプルの結合として注釈を付けることでした。

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 には、紛らわしいエラーが多数表示されます。

また、関数自体を個別の関数全体の定義の結合として入力しようとしましたが、それは 機能しませんでした のいずれかです。

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

複数の可能なコールシグネチャを持つ型注釈関数にどのようにアプローチすべきですか? (または、マルチ署名はFlowのアンチパターンと見なされており、私はまったくそうすべきではありません。その場合、それを行うサードパーティのライブラリとやり取りするための推奨されるアプローチは何ですか?)

39
callum

表示されているエラーは、コードのバグとFlowのバグの組み合わせです。

コードのバグ

バグを修正することから始めましょう。 3番目のelseステートメントで、間違った値を

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

配列アクセスを正しいインデックスに変更すると、2つのエラーが削除されます。これがFlowの目的であり、コード内のエラーを見つけることに同意できると思います。

絞り込みタイプ

問題の根本的な原因に到達するために、エラーがどこにあるかをより明確にして、問題が何であるかをより簡単に確認できるようにすることができます。

_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]_がどうあるべきかが非常に明確であるためです。これにより、1つのエラーが残ります。

流れのバグ

残りのエラーはフローのバグです: https://github.com/facebook/flow/issues/3564

バグ:タプルタイプが長さのアサーションと相互作用していません(.length> = 2および[] | [number] | [number、number]タイプ)

オーバーロードされた関数を入力する方法

フローは、この場合のように、さまざまなタイプの多様性に対処するのが得意ではありません。 Variadicsは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がそれぞれの方法を試すことができるようにします。

9
EugeneZ

&で結合することにより、複数の関数シグネチャを定義できます。

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

試してみてください

8
Pavlo

あなたが与えた3つの可能なトレーニングのうち、単一のオプションオブジェクトを使用してそれを機能させる方法を理解しましたが、少なくとも1つのオブジェクトを設定する必要があるため、それぞれの可能性を定義する必要があります。

このような:

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