TypeScript初心者の時に知りたかったこと
はじめに
前の記事でもあげていたMRTを実際の実装で使っている時、型の予測変換が大変便利でした。
具体的には、よくわからないメソッドやプロパティに対して、しっかりと説明が書かれており、TSDocの中に該当のリンクがあって簡単に公式ドキュメントに辿り着けました。
大変便利なTypeScript、しかしエンジニア1年目の時の私はかなり苦しめられた記憶があります。
なので一年目に知りたかったことをまとめ、簡単なTypeScriptのおさらいをしよう!という試みの記事です。
内容は入門レベルなのであらかじめご了承ください。
型の必要性とよく使用されるイニシャルの早見表、詰まりやすい型の簡単な紹介とTypeScript筋の鍛え方のすすめについて、簡単にまとめていきます。
困った時に気軽に見返すことのできる記事になりますように!!
それでは早速、行ってみましょう。
そもそも何で型が必要なの??
一般的なWebシステムの開発などバックエンド領域でデータを扱う際は、そのデータがどのような形式か定まっていなければいけません。
例えば、”2024”
これは文字列なのか、数字なのか、はたまた日付なのか。我々人間はその前後の文脈などからある程度推察できるかもしれませんが、プログラムの世界ではそうはいきません。
このデータがどんな形式なのかをプログラム内で定義する必要があります。そうしなければシステムは予期せぬ動きをしてしまいます。また、せっかくバックエンド側で正しくデータ形式を定義しても、フロント側から間違ったデータを渡してしまうと、システムは予期せぬ動きをしてしまいます。
上記からわかるように、フロントエンド領域でもさまざまなデータを扱います。そしてデータの受け渡しなどの際に活躍するのがJavaScriptですが、JavaScriptにはデータの形式を制御するための機能がありません。
そこで生まれたのがTypeScriptというわけです。 JavaScriptの書き方のままで定数や変数にデータ形式を定義することができ、定義と異なるデータが宣言された時はエラーを出して開発者に教えてくれるのです。
ちなみになぜJavaScriptが型定義ができないかなどは、JavaScriptの成り立ちなどいろいろ理由があると思うのですがここでは割愛します。こちらの記事がとっても分かりやすかったので興味のある方はぜひ。
具体的にJavaScriptとTypeScriptの簡単なコードを見比べてみます。
// data は文字列を受け取ります。数字の場合エラーとなります
const moji = (data)=> {
...
return
}
こんな記述はしないかもしれませんが、JavaScriptの場合どんな引数を与えればいいかなどの情報は引数の名称やコメントなどでしか伝える手段がなく、間違った関数を渡したとしてもエラーに気づけないという問題がありました。
TypeScriptだと以下のように書くことができます。
const moji = (data:string)=> {
...
return
}
断然便利。TypeScriptは人類の叡智かもしれません。
さらに間違った定義をした場合、すぐにエラーで教えてくれます。(これに苦しむこともありますが)
ちなみに個人的な感想ですが、HTMLやCSS主体のWebサイトの制作などではTypeScriptはあまり恩恵を感じることはないかもしれません。逆にヘッドレスCMSなどAPIを活用するサイトやさまざまなデータを取り扱いバックエンドと疎通するWebアプリケーションのフロント制作にはTypeScriptは欠かせないと思っています。
困りやすい型パターンをおさえよう
さて、ここからは横着TypeScript編です。
TypeScriptエラーに出くわすと赤並線がたくさんでてくるし、エラーを覗くと英語ばっかりだし、なんかアルファベットだけの文字あるし…みたいな初心者にはとても辛い現象が待ち受けています。
ですが、全ては無知ゆえ。知らないから人は恐れるのです。ゆえに答えはただ一つ、知ること。
だからと言って公式のドキュメントを一から十まで読み漁るのはとても時間がかかります。みっちり勉強するのはもちろん大切ですが、まずは最低限わかっておきたいところをおさえたいのが人の性。
なのでこの記事では私個人がよく詰まっていた(今もたまに詰まる)ポイント以下3つに注力してまとめます。
- ジェネリクス
- Record型
- unknown型
基本さえしっかり覚えれば応用が効くし、あとはChatGPTがなんとかしてくれます。
忘れても大丈夫、この記事を読み返せばいいのです。
ジェネリクスについて
ジェネリクスは一言で言うと、型も変数のように扱えるようにすることです。処理自体は変わらないけど、引数の型が変わる関数を定義する場合などに用いられます。
具体的にみてみましょう。下記のような二つの関数があったとします。”…”は 共通の内容と仮定します。
// 文字列を受け取り、文字列を返す関数
function sampleFunction(v1: string, v2: string): string {
...
return v1
}
// 数値を受け取り、数値を返す関数
function sampleFunction(v1: number, v2: number): number {
...
return v1
}
どちらも同じ処理をする関数ですが引数の型が違います。これを統一して定義し、関数呼び出しの際に型定義しよう!というのがジェネリクスです。具体的に呼び出す際の記述をみてみます。
// 定義
function sampleFunction<T>(v1: T, v2: T): T {
...
return v1
}
// string型で呼び出し
const callAsString = sampleFunction<string>("1","2")
// number型で呼び出し
const callAsNumber = sampleFunction<number>(1,2)
上記で”T”とした変数は慣用的に”Type(型)”を表しています。詳しくは後述の早見表をご覧ください。
ポイントは型定義の変数化です。この基礎を理解しなければ、難解な型パズルを解くことができない基本中の基本です。
ジェネリクス自体の仕組みは簡単ですが、組み合わせにより複雑怪奇になっていくので、一つ一つの意味を掴んでいくのが大事です。
ジェネリクスでよく見かけるアルファベット1文字早見表
TypeScriptのドキュメントや参考記事には当然のようにアルファベット1文字が出てきます。開発慣れした人からすると大した問題ではないのですが、初心者からすると急にアルファベット1文字出されても「はて?」となります(個人の意見)。
なので早見表を作りました、困った時は見返します!ちなみに、T、K、Uあたりを抑えておけば困らない気がしてます!
※以下のアルファベットはあくまで慣習的に使われているものであり、プロジェクトによっては同名でも異義のものが存在するかもしれません。あくまで一般的な略称のまとめです。
1. T
- 略称: Type
- 意味: 一般的な型を表す。汎用的な型パラメータとして最もよく使われる。
function identity<T>(arg: T): T { return arg; }
2. K
- 略称: Key
- 意味: オブジェクトのキー型を表す。主にキー制約に関連して使用される。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; }
3. V
- 略称: Value
- 意味: 値型を表す。キーと組み合わせて使われることが多い。
type KeyValue<K, V> = { key: K; value: V; };
4. E
- 略称: Element
- 意味: 配列や集合の要素型を表す。
function mapArray<E>(array: E[], callback: (item: E) => E): E[] { return array.map(callback); }
5. U
- 略称: Union
- 意味: Union型を表す。あるいは別の型を受け取る汎用的な型として使用される。
function combine<T, U>(a: T, b: U): T & U { return { ...a, ...b }; }
6. P
- 略称: Property
- 意味: プロパティ型を表す。
type Pick<T, P extends keyof T> = { [K in P]: T[K] };
7. R
- 略称: Return
- 意味: 関数の戻り値型を表す。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
8. C
- 略称: Context
- 意味: コンテキスト型を表す。特定の文脈に関連する型として使用されることが多い。
function withContext<C>(context: C): void { /* ... */ }
9. N
- 略称: Number
- 意味: 数値型を表す。主にジェネリック制約で使われる。
type ArrayLength<N extends number> = N[];
10. S
- 略称: State
- 意味: 状態型を表す。状態管理やステートに関連する場合に使用される。
function useState<S>(initialState: S): [S, (newState: S) => void] { /* ... */ }
Record型について
Record型はプロパティのキーの型、中身の型をそれぞれ定義することができます。特定のキー名に対し特定の型の中身を定義したい場合によく使用されます。
また、型定義の中でさらに厳密にキー名を定義したい時などに便利です。
最も簡単な例は以下の通りです。オブジェクトの中のキーは文字列、中身はnumberと定義できます。
type Sample = Record<string, number>;
const value: Sample = {
a: 1
};
上記の場合自由度が高く、あまりRecord型の恩恵を感じないかもしれません。下記の例をみてみましょう。
type Status = "active" | "inactive" | "pending";
type StatusMap = Record<Status, string>;
const statuses: StatusMap = {
active: "User is active",
inactive: "User is inactive",
pending: "Approval pending",
};
StatusMapは1行目で定義したリテラル型をキー名に指定し、中身を文字列と定義しています。
定数 statuses は必ず”active” , “inactive” , “pending”というキー名を持ちます。このような形で型を定義することで、定数に一貫したプロパティを持たせることが可能です。
ポイントはキー名に型を指定できること、まずはここだけおさえましょう。
unknown型について
こちらはany型と同じようになんでも入れられるという型です。
any型と違う点として、unknown型が指定されたデータは値に対する操作が制限されます。つまり参照はできるけど、値の変更(特定の型への代入など)はできないという事になります。
具体的な記述を見てみましょう。
// any型の場合
let anyValue: any = "Hello, world!";
console.log(anyValue.toUpperCase()); // 問題なく動作するが、型安全性はない
// unknown型の場合
let unknownValue: unknown = "Hello, world!";
console.log(unknownValue.toUpperCase()); // エラー: 型が`unknown`であるためプロパティ 'toUpperCase' にアクセスできません
// 代入について
anyValue = 42;
let num: number = anyValue; // エラーなしで代入可能
// 型の代入も制限される
unknownValue = 42;
let anotherNum: number = unknownValue; // エラー: 型 'unknown' を 'number' に割り当てることはできません
上記はそれぞれエラーになりますが、型ガードを使うとそれぞれ操作することができます。型ガードは簡単にいうと特定の型の時のみ実行を許す条件分岐です。
// 型ガードを使用して明示的に操作する
if (typeof unknownValue === "string") {
console.log(unknownValue.toUpperCase());
}
if (typeof unknownValue === "number") {
let anotherNum: number = unknownValue; // 安全に操作できる
}
また、型安全性という観点からはあまりおすすめできませんが、下記のような記述をすることもあります。こちらはnumber型をstring型に変更する際、一度unknown型を経由する方法です。
ライブラリなどの型制約がどうしても厳しすぎる時の奥の手というイメージです。
let value: number = 42;
// 一度 `unknown` を経由する
let stringValue: string = value as unknown as string;
console.log(stringValue); // 42(実行時には文字列型として扱われる)
もっとTypeScriptと仲良くなりたいあなたへ
さて、ここまで読んだあなたはもっとTypeScriptとお友達になりたくなってきたはず。そんな方にはこちらがおすすめです。
こちらのリポジトリにはさまざまな難易度の型パズルが提供されており、TypeScript筋をバキバキに鍛え抜くことができます。
始め方はとても簡単。課題集から挑戦したい問題を選択するだけです。
左上の挑戦するを押すとWeb Editorにリンクが遷移します。
さまざまな人の回答も閲覧することができるので、どうしてもわからない時でも安心ですね。
型マッチョになりたい方はぜひお試しあれ。
おわりに
フロントエンド開発に慣れた人が見れば、「なにを当たり前のことを」と思われちゃうかもしれませんが、初心者やWeb開発が全くの未経験の場合は何がわからないかもわからないことが多いです。
また、いったん覚えたこともしばらく離れると意外と忘れてしまうのが人間です(私はすぐに忘れます)。なので、こういう超入門記事もいつか記憶を無くした時の自分のためにと思って書いています。
TypeScriptはしっかり使用するとこれ以上ないくらい便利だと思います。そのためにもまずは基礎をおさえて、時間をかけて勉強して、自分の武器にしていきたいものです。
誰がみてもわかりやすい、開発者思いのコードを書けるようになりたい!!!
以上、遠藤でした〜!ではまた〜!