1 of 19

TypeScriptのタプル型推論使って型を改善

by sisisin

2 of 19

自己紹介

株式会社オプト シニアエンジニア @sisisin(しめにゃん)

  • [GitHub](https://github.com/sisisin)
  • [Twitter](https://twitter.com/_sisisin)
  • フロントエンドの人だけどスクラムマスター・インフラ・サーバーサイドといろいろやります
  • TypeScript・静的型付けが好き
  • 最近はtypelessというReact向けの状態管理ライブラリにハマってて、使い込んだりコントリビュートしてます

3 of 19

TypeScriptの型定義でよくみるやつ(RxJSのObservableの型定義より抜粋)

4 of 19

何かというと、可変長引数や配列を受け取ったときに、Array<T>のように一緒くたな型ではなく、それぞれの要素を区別して扱いたい、というケースです

TypeScriptの型定義ではこの例のように泥臭く可変長引数の1つずつに対して型付けしているケースが良く見られます

今回はこれを改善した(ようとしている*)という話をします

*諸般の事情でまだPR出してないので。。近いうち出します

5 of 19

参考にした資料

kgtkrさんの TypeScriptで型安全なobjectのpick関数を定義する(型パラメーターを単一のリテラル型に制限する方法) 

TypeScript 3.0のExtracting and spreading parameter lists with tuplesで遊ぼう

uhyoさんのTypeScriptの型初級

この辺の記事をめっちゃ参照して今回の改善に至りました

この場を借りてお礼申し上げますthx!

6 of 19

どこの何を解決した?

自分がコントリビュートしているtypelessというライブラリで、まさにこのようなシグネチャの関数があったので試してみました

`useMappedState` という関数を例に話します

links:

  • doc
  • code

7 of 19

どこの何を解決した?

参考までに、今回の改善した型を試せるようにTypeScript Playground上に サンプル を置いておきました

もしよければ御覧ください

8 of 19

before

9 of 19

解説

第一引数(stateGetters): StateGetter<T>のタプル

第二引数(mapper): コールバック関数。コールバック関数の引数のn番目が、第一引数のn番目のTとなる

例えば `useMappedState([sg1,sg2], (s1, s2) => {})` という記述をした場合、

  • sg1: StateGetter<T1>,sg2: StateGetter<T2>
  • s1: T1,s2: T2

という具合です

10 of 19

解説

元の実装では、この `stateGetters` という `StateGetter<T>` のタプルの型を、要素の数に対応する分だけで定義していました

  • `stateGetters: [StateGetter<T1>]`
  • `stateGetters: [StateGetter<T1>, StateGetter<T2>]`
  • `stateGetters: [StateGetter<T1>, StateGetter<T2>, StateGetter<T3>]`

のようなイメージ(実際のコードはリンクから見てもらえればと)

11 of 19

after

12 of 19

after解説

関数の第一引数 `stateGetters: T` について:

  • 型引数の第一引数 `T extends [] | [StateGetter<any>, ...StateGetter<any>[]]` にて、`StateGetter<any>` に所属する型を要素とするタプル型である、という制約を設けている
  • 記法がやたら複雑ですが、このように書くと `StateGetter<T>` のタプル型と認識されます(見た目上は配列型っぽく見えますが)

13 of 19

after解説

第二引数 `mapper` について

大変読みにくいのですが、単純化すると、 `mapper: (...args: ComplexType) => R;` です

14 of 19

after解説

この `ComplexType` がなにかというと、先程解説した「コールバック関数の引数のn番目が、第一引数のn番目のTとなる」に該当する表現です

`stateGetters: T` の `T` を元にして、この `mapper` 関数の引数の型を推論させる、というものになります

詳しい記法については端折りますが、 `StateGetter<X>[]` に所属する型を元に、タプルの要素一つ一つに対して、`X` を抽出する、という事を書いています

15 of 19

after(再掲)

16 of 19

何が嬉しいの?

  • 利用者は、引数の数の制限がなく使えるようになる(beforeでは定義されてる数を上限として、それ以上使えない)
  • メンテナは、シグネチャを変える時などにメンテナンスが楽になる

17 of 19

というわけで、ライブラリの型をタプル型推論を利用して改善したという話でした

18 of 19

余談

`createSelector` という関数も似たシグネチャなんですが、(自分では)解決できなかったので誰か挑戦者求ム

code

doc

19 of 19

Happy Hacking!