入門編で、TypeScriptの知識を紹介したので、本編では基本の型システムの記法などをなるべく簡単に紹介していきたいと思います。
プリミティブ型
真偽値、数値、文字列などのプリミティブ型は基本です。
変数/定数名の後に:型名
をつけます。
const bool: boolean = true;
const str: string = "Hello World";
const num: number = 2021;
let bool: boolean = true;
bool = "hoge" //コンパイルエラーになる
↑のサンプルの様に、真偽値型
の変数に文字列型
を入れようとすると、このようにboolに赤線がついてTSがエラーを吐いて教えてくれます。
リテラル型
リテラル型はプリミティブな値そのものを型として表現するものです。
let str: 'Hello';
str = 'Hey' //コンパイルエラーになる
↑のサンプルは変数strを'Hello'のみを取り得る'Hello'型
として定義されてます。しかし、別の文字列をいれようとしてるので、これはエラーになります。リテラル型は後述のユニオン型と合わせて使う事で真価を発揮します。
配列型
配列型の記法は2種類あって、どっちも同じです。各現場のルールに従いましょう。
const arr: number[] = [1, 2, 3];
const arr: Array<number> = [1, 2, 3];
オブジェクト型にはinterfaceを使う
オブジェクト型を扱うときはinterfaceを使って名前をつけて扱うと便利です。
interface NAME {
first: string;
last: string;
}
const nameObj: NAME = {
first: "Yamada",
last: "Taro"
}
また、interfaceは拡張
する事もできます
//extendsを使う
interface NAME {
first: string;
last: string;
}
interface PROFILE extends NAME {
age: number;
}
const profile: PROFILE = {
first: "Yamada",
last: "Taro",
age: 30
}
//同名で複数回定義すると、後続の宣言が型を拡張する
interface NAME {
first: string;
last: string;
}
interface NAME {
age: number;
}
const profile: PROFILE = {
first: "Yamada",
last: "Taro",
age: 30
}
オプション(?をつけると省略可能なプロパティにできる)
サンプルではjobプロパティに?
をつけて省略可能なオプションプロパティとしています。こうする事でjobがundefined
になりうる可能性を持ちます。
interface PROFILE {
name: string;
age: number;
job?: string;
}
const profile: PROFILE = {
first: "Yamada Taro",
age: 30
}
//jobはオプションなので無くてもエラーにならない
関数型
引数の括弧の後に返り値の型を定義します。
// このfunc1の返り値はnumber型
const func1 = (x: number, y:number):number => {
return x + y;
}
ユニオン型(合併型)
指定された複数の型
のいずれかに当てはまればOK
let value: boolean | number;
value = true
value = 10
value = "hello" //コンパイルエラーになる
//nullを許容する場合
let value: string | null;
//配列の要素にUnion Typesを使う場合
let arrayUni: (number | string)[];
arrayUni = [0, 1, 2, "hello"];
// リテラル型とユニオン型の組み合わせ
let gafa: "Google" | "Apple" | "Facebook" | "Amazon"
gafa = "Amazon"
gafa = "Nike" //コンパイルエラーになる
インターセクション型(交差型)
複数の型を&
でつないで定義する型
interface Profile = {
age: number;
city: string;
}
interface Login = {
username: string;
password: string;
}
const userA: Profile & Login = {
age: 30,
city: "tokyo",
username: "yamada taro",
password: "dffsaaa"
}
型エイリアス
type構文を使い、任意の方に名前(エイリアス)をつけることができる。
type UserId = string;
type Company = "Google" | "Apple" | "Facebook" | "Amazon"
type User = {
id: UserId;
company: Company
}
interfaceとの違いは?
似てますが違います。実用上での注意があり、interfaceは前述の通り同名で定義する事で拡張が可能ですが、typeはそれができません、エラーになります。
ジェネリクス
TypeScriptの中でも重要な要素のひとつ
具体的な型ではなく、抽象化された型
を扱う為にジェネリクスという機能は存在する。
サンプルを見てみましょう。
//このinterfaceを定義した段階ではlistの型は決まってない。あくまでもテンプレート
interface Hoge<T> {
item: T;
}
//Hogeの利用側で <> の中に型パラメータと呼ばれる任意の型を指定して定義する事が出来る
const hoge1: Hoge<string> = { item: "Hello" };
// itemはstring型になる
const hoge2: Hoge<number> = { item: 2021 };
// itemはnumber型になる
抽象的な型のテンプレートを用意し、実体を作る時に具体化する、つまり動的に設定する事が出来るという事ですね。この機能によって様々なユースケースで共通して使う事ができます。型推論が効く場合は利用側で明示的に型パラメーターを指定する必要はありません。ジェネリクスは型エイリアスでもClassでも関数コンポーネントでも使えます。
型推論
TypeScriptは変数に型を必ず付与する必要はなく、宣言時に代入された値から、その値の型をTSが推論してくれるからです。例えばReactのステートフックの話になりますが、以下のように型を付与してます。2つ目のshowMenu
に関しては型推論が効くので、はあってもなくてもOK。
const [name, setName] = useState<string>("");
const [showMenu, setShowMenu] = useState<boolean>(false);
constアサーション
通常のObjectや配列の中身は変更できてしまうけど、as constを使ってread only, 変更不可にでき型を安全に守る事ができる。
const FixedData = {
name: 'hoge'
} as const;
コードを書いている時点でエラーに気づける、これがTSの恩恵。
TypeScriptの公式ページにこんな記述があります。
型は、コードの品質と読みやすさを高めることがGoogle、Microsoft、Facebookによって実証されています。
・型があることによって、コードを書いている時点でエラーに気づくことができます。そして、すぐにエラーを修正できます。ランタイム(実行時)で、はじめてエラーに気づいて、コードに戻って修正するよりも、ずっと効率的です。開発中に早い段階でエラーに気づけるということは非常に素晴らしいことです。
・型は、それ自体が、完璧なドキュメントです。関数のシグネチャは定理であり、関数の本体は証明です。
コードを書いている時点でエラーに気づくことができ、すぐに修正できるという事がTypeScriptの恩恵ですね。
まとめ
自分の働く現場では、全てにガチガチに型を持たす事は無理なので、型に迷う時はスピード重視でとりあえずはany型で通して、レビューで型をどうするかアドバイスもらったりすればいいという方針で進めています。
よくTypeScript導入したはいいけど、any型だらけになって、結局やめたという話は勉強会などで耳にした事があるので、そうはならない様にはしたいですね。
個人的にTypeScriptが導入されて当然開発のスピードが落ちましたが、初期導入にかかるこの苦労を乗り越えれば入門編で紹介した型の恩恵に預かれると思うので頑張って使っていきたいと思います。
本編では個人的に最低限これだけは抑えておけば大丈夫というものだけを紹介しました。なので、これで全てではありません!
また新たに学びがあれば加筆していきます。
参考書籍:WEB+DB PRESS Vol.117
実践編へ続く。