TypeScriptで複雑な型をつける
なぜ複雑な型を付けるか
TypeScriptは、ジェネリクスやConditional Typesなどを使うことでTypeScriptの恩恵に最大限あずかることができる。ジェネリクスやConditional Types抜きでTypeScriptを使う場合、オブジェクトの複雑な操作や動的な型操作で推論に頼れず、stringやnumberといったプリミティブで我慢することになる。これに我慢せず型の拡大を防ぐことで、開発時にエラーを検出したい。
学習リソース
TypeScriptの学習リソースのなかで型アノテーションを超えたTypeScriptの使用法は少ない。これらを収集する。
公式ドキュメント
手法のみ書かれていてモチベーションをつかみにくいが、困ったときにどこを調べればいいか分かるよう知識のインデックスをつけておきたい。
Type Challenge
公式ドキュメントを一通りさらったら、手を動かしたほうがよい。各問題で型を付けたい部分とテストケースが提示され、テストケースの赤線がなくなればクリアになる。
// expected to be string
type HelloWorld = any
// you should make this work
type test = Expect<Equal<HelloWorld, string>>
解けないときは、リポジトリのissueのユーザー解答やType Challenges Solutionsを参考にするとよい。
初級でもエッジケースの対処が難しかったりするので、自分はバンバン解答を見ていった。
初級ができたなら、あとは既知の道具の組み合わせでクリアできるはず。
実際のユースケースを知る
辞書を作る関数に TypeScript で執拗に型をつける
型定義をレベル1(ゆるゆる)からレベル5(がちがち)に締めていくハンズオン形式の記事。型定義をかく際に考慮すべき割れ窓や、その塞ぎ方について実践的に学ぶ事ができる。
TypeScriptの表現力で自由なJavaScriptに立ち向かう 〜 Immutable.js 編 〜
Mastering TypeScript Template Literal Types
TypeScirpt 4.1から導入されたテンプレートリテラル型のよいintroduction.
オブジェクトのdeep merge
入れ子状のオブジェクトがある場合、内部のプロパティのマージに対応するには型のマージを再帰的に適応する必要がある。
type Merge<T1, T2> =
{
[P in Exclude<keyof T1, keyof T2>]: T1[P]
} & {
[P in Exclude<keyof T2, keyof T1>]: T2[P]
} &
{
[P in (keyof T1) & (keyof T2)] : T2[P] extends object
? Merge<T1[P],T2[P]>
: T2[P]
}
object
でノンプリミティブオブジェクトか判定する
Tip:プリミティブでないオブジェクトの判定にはObject
が使えないため、ただのオブジェクトの判定を素朴に実装するとこうなる。
type Primitives = number | string | boolean | symbol | bigint
type isPrimitive<T> = T extends Primitives ? 'primitive' : 'non-primitive'
Object
のかわりにobject
を使うことで記述を簡略化することができる。
object
はnon-primitive typeだけを含むため。
この存在は、TypeScript 2.2のリリースノートにだけひっそりと記載されている
なお、Functionなどはobjectに含まれることに注意が必要。
type xxxxx = Function | Array<any> | [] | MapConstructor extends object ? true : false // true