🤘

1日1問 感謝のtype-challengesでTypeScript力向上

2023/06/01に公開

新卒2年目エンジニアのyutake27です。

約4ヶ月間、(ほぼ)1日1問 type-challengesを解いていたら、いつの間にか型の力(≒TypeScript力?)が付いていたのでその振り返りとtype-challengesのおすすめをしようと思って記事を書きました

type-challengesって?

https://github.com/type-challenges/type-challenges

TypeScriptの型の問題集です。
初級〜最上級までレベル別に分かれており、徐々にステップアップしながら型を学習することが可能です。コードの実行環境も用意されているのでブラウザ上だけあれば実際に手を動かしながら学ぶことができます。

例えば初級編1問目のPickは以下のような問題です。

組み込みの型ユーティリティPick<T, K>を使用せず、TからKのプロパティを抽出する型を実装します。

この問題に対していくつかのテストケースが用意されているので、そのテストケースが通るように型を実装するというものです。(実際にチャレンジしてみたい方はこちらからチャレンジできます)

type-challengesのやり方に関しては色々な記事が出ているので詳しくはそちらをご覧ください。

https://qiita.com/ryo2132/items/925b96838dd8cca7cebd

https://zenn.dev/praha/articles/9494d8861c95fd

1日1問をはじめたきっかけ

たまたまtype-challengesのすゝめという記事を見かけて、面白そうだなと思って会社slackで呟いたところ、先輩から1日1問やって解答解説流して〜と言われ、面白そうだったのではじめてみました。

結局その先輩も毎日解くのに付き合ってくれて、約4ヶ月かけて(ほぼ)全問解くことができました。

1人だと継続が大変なので誰かと一緒にやるのがおすすめです。(先輩に感謝)(1人で1日1万回正拳突きしていた某会長はすごい)

type-challengesはじめる前の筆者の型力

TypeScript歴半年程度でbooleanやstringなどのprimitive型やUnion型くらいは分かるが、それ以外は怪しい(例えばtypeofextends, inferの使い方など)という感じのレベルでした。

type-challenges初級編1問目のPickも自力で正解できなかった気がします。

type-challengesを経た成長

ユーティリティ型がパッと出てくるようになった

TypeScriptには一部のプロパティだけを取得するPickやプロパティを必須にするRequiredなどの便利なユーティリティ型(Utility Types)があります。

ユーティリティ型の中で、実装中によく使うものはパッと出てくるようになりました。

例えば、オブジェクトのあるプロパティだけ不要だな〜

type Props = {
  a: boolean;
  b: string;
  c: number; // こいつだけ不要
}

Omit<Props, 'c'>でcプロパティを除外!

Unionの一部だけ必要だな〜

type A = 'hoge'
type B = 'fuga'
type C = 'piyo'

type All = A | B | C // Aだけ必要

Extract<All, A>でAだけ取得!

みたいなのが瞬時に出てくるようになりました。

ユーティリティ型は存在を知っているか知らないかで差がつくので一通りどんなものがあるか眺めておくだけでも役に立つかもしれないです。

業務でもやや難しめの型が書けるようになってきた

業務で型を書くときにも、type-challengesをやる前だったらかなり苦戦していたであろう型を書けるようになってきて、成長を実感しました。

例えば、Recordの一部のプロパティを必須にする型が必要になった際、
OmitとPickとRequired組み合わせて↓みたいにすればいける!みたいな発想が出てくるようになりました。(これはtype-challengesに同じ問題がありました)

type PartialRequired<T extends object, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>

また、上のPartialRequired型を第一引数のTがUnionの時(type T = Hoge | Fugaみたいな時)にも動作するようにしなければならない際、

union distribution使って↓みたいにすればいける!みたいな発想も出てくるようになりました。

type PartialRequired<T extends object, K extends keyof T> = T extends T
  ? Required<Pick<T, K>> & Omit<T, K>
  : never

type-challengesをはじめる前はunion distributionを知ってさえいなかったので完全にtype-challengesのおかげです。(union distributionってなんやねんという方はこちらの記事が参考になります)

独力で実装できなくても、type-challengesで確か似たような問題やったな〜 → この問題だ! → この解き方真似すればいける!みたいに似た問題を思い出すことで、参考実装を探すことができ、実装できたこともありました。

実装スピードが上がった

初めのうちは初級編の簡単めな問題でも解くのに30分くらいかかっていました。

1日1問をはじめて3ヶ月後くらいには上級編でも問題によっては3分程度で解けるようになっていて、実装スピードの向上を感じました。

解くのにかかった時間は記録しておくと後から見返したときに成長を感じれて良いかもしれないです。

型を絡めた難しいコードが読めるようになってきた

例えば↓みたいなコードがあったとします(参照

type ElementOf<A extends any[]> = A extends (infer Elm)[] ? Elm : unknown;
type IsNever<T> = T[] extends never[] ? true : false;

function allElements<V>(): <Arr extends V[]>(arr: Arr) =>
  IsNever<Exclude<V, ElementOf<Arr>>> extends true ? V[] : unknown {
  return arr => arr as any;
}

何やらElementOf, IsNeverという型と、AllElementsという関数が定義されています。

正直type-challengesをやる前の自分だったらA, V, Arrって何?extends, inferって何?IsNeverって何してるの?という感じで分からないことだらけで、コードを理解するのを諦めていたと思います。

ですがtype-challengesをやったことで、ElementOf, IsNeverそれぞれがやっていることを理解できるようになり、それを利用しているallElementsの働きを理解できるようになりました。

ライブラリのコードはこんな感じに型をうまく利用しているコードも多く、このような実装を理解できるようになってきたのも非常に成長を感じています。

終わりに

type-challengesのおかげで型の力がぐんぐん付いてきました。
また、型で色んな表現ができることを知ってTypeScriptの型が好きになりました。

型力付けたいな〜という方や型よく分かってないんだよな〜という方はぜひtype-challengesやってみてください!初級編だけでもかなり知識がつくはずです!

追記

type-challengesからおすすめの問題をピックアップした記事を書いたので宜しければそちらもご覧ください。

https://zenn.dev/yutake27/articles/6d117390805af0

Discussion