Closed8

TypeScriptで複雑な型をつける

noyannoyan

なぜ複雑な型を付けるか

TypeScriptは、ジェネリクスやConditional Typesなどを使うことでTypeScriptの恩恵に最大限あずかることができる。ジェネリクスやConditional Types抜きでTypeScriptを使う場合、オブジェクトの複雑な操作や動的な型操作で推論に頼れず、stringやnumberといったプリミティブで我慢することになる。これに我慢せず型の拡大を防ぐことで、開発時にエラーを検出したい。

noyannoyan

学習リソース

TypeScriptの学習リソースのなかで型アノテーションを超えたTypeScriptの使用法は少ない。これらを収集する。

noyannoyan

公式ドキュメント

手法のみ書かれていてモチベーションをつかみにくいが、困ったときにどこを調べればいいか分かるよう知識のインデックスをつけておきたい。

noyannoyan

Type Challenge

公式ドキュメントを一通りさらったら、手を動かしたほうがよい。各問題で型を付けたい部分とテストケースが提示され、テストケースの赤線がなくなればクリアになる。

// expected to be string
type HelloWorld = any
// you should make this work
type test = Expect<Equal<HelloWorld, string>>

解けないときは、リポジトリのissueのユーザー解答やType Challenges Solutionsを参考にするとよい。
初級でもエッジケースの対処が難しかったりするので、自分はバンバン解答を見ていった。
初級ができたなら、あとは既知の道具の組み合わせでクリアできるはず。

noyannoyan

オブジェクトの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]
}

Playground Link

Tip:objectでノンプリミティブオブジェクトか判定する

プリミティブでないオブジェクトの判定には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

このスクラップは2021/11/30にクローズされました