タオルケット体操

サツバツいんたーねっとでゲームとかガジェットのレビューとかをします

TypeScriptで既存の関数の引数と返り値の型情報をコピーする方法

Sponsored link

TypeScriptにはType infer in ConditionalTypeという便利機能があり、それを利用すると既存の型定義から柔軟に特定の方を取り出すことができます。
そして利用頻度が高そうなものについては組み込みの型定義がいくつか存在します。

関数の型定義から引数を取り出すのは Parameters<T> で、この型引数に関数の定義を与えることでその関数の引数がTupleとして返ってきます。
ちなみに返り値は ReturnType<T> で取り出すことが出来ます。

function testFunc(a: string, b: number, c: boolean): {a: string, b: number, c: boolean} {
  return {a, b, c};
}

type TestFuncArgs = Parameters<typeof testFunc>;
// TestFuncArgs = [a: string, b: number, c: boolean];

type TestFuncRet = ReturnType<typeof testFunc>;
// TestFuncRet = {a: string, b: number, c: boolean};

はい。
魔法感ありますが、ただの標準機能を使って作られたUtilityなので自分で同様のものを定義することも可能です。

次に型定義ですね。
TypeScriptの可変長引数は型定義にTupleを渡すことが出来ます。つまり

function testFunc2(...args: TestFuncArgs) {
  const [a, b, c] = args;
  testFunc(a, b, c);
}

という定義が可能なわけですね?

使いみち

「こんなんどこで使うんだよ」と思われるかもしれないですが、高階関数の定義がやりやすくなります。
以下はサンプルのためのサンプルで実用性はありません。

function applyThenLogPromise<Func extends (...args: [...any[]]) => Promise<any>>(p: Func, ...args: Parameters<Func>) {
  return p(...args)
    .then(v => {
      console.log(v);
      return v;
    });
}

function dummyPost(path: string, param: any): Promise<{result: string}> {
  return Promise.resolve({result: 'success'});
}

applyThenLogPromise(dummyPost, '/test', {id: 1, name: 'test'})
  .then(v => v.result);  // 返り値もちゃんと推論してくれてる

とまぁこんな具合ですね。
ユニットテストとか、Reduxのユーティリティだとか、そういった関数に型をつけるときに役に立つテクニックなんじゃあないでしょうか。レッツ型パズル!

あとReactを書いていて、サードパーティコンポーネントに渡すCallBackの引数が複雑で型定義を書くのがめんどくせぇなぁってときにも使えます。

import {FancyComponent} from 'fancy-component';

function HogeComponent(props: any) {
  const handleFunky = (...args: Parameters<typeof FancyComponent['onFunky']>) => {
    // handle
  };

  return <FancyComponent onFunky={handleFunky} />;
}

可読性の観点で言うと、はちゃんと書いたほうが作法としては丁寧かなぁ?とはおもいますが、型検査はちゃんと動くしエディタの補完もバッチリなんで僕一人しか触らないことが確定の状況でコード書くときは僕はだいたいこうしてますね。人間よりコンパイラが賢い。
特にTypeScript対応が未熟なコンポーネントライブラリを使う場合、ハンドラの定義がドキュメントを読んでもわからなくて.d.tsを読みに行かなければならなかったり、定義がバギーだから頻繁にパッチあたって壊れたりするんでこっちのほうがベターまであるんじゃないかなとおもいます。
用法用量を守って利用しましょう。ご意見お待ちしてます。

なおReactのコンポーネントのイベントハンドラはだいたいoptionalで定義されているため、高確率でParametersの定義を満たせません。
そういうときは Exclude<typeof FancyComponent['onFunky']>, undefined> とすればoptionalからundefinedを除外できます。

以上です。