筆者がTypeScriptを学ぶ上で読んだ プログラミングTypeScript スケールするJavaScriptアプリケーション開発」を読んで終わりにしないために、記事という形でアウトプットしたかったので投稿します。
本投稿は2本目です。前回の記事のリンクも貼っておきますのでよければ見てみてください。
〜 じっくり入門 TypeScript 〜 1.全体像
型(type)とは
値と、それを使ってできる事柄の集まり
とありますが、これだけではイマイチよくわかりません。具体例は以下です。
・boolean型はtreuかfalseの値をとり、||
や&&
、!
などの演算や操作を行うことができる
・number型は全ての数値をとり、四則演算や、それらについて関数を呼び出すことができる(toFixed
、toString
など)
といった具合で、その値とその値をどのように操作できるか(また、何をすることができないか)をまとめたものを型と呼びます。
また以下はTypeScriptにおける型の階層構造になります。本記事においてたまにこの階層構造を使った説明がでてくるので、参考にしながら読み進めていただければ良いと思います。
型の初歩
ここからが本題です。TypeScirptがサポートしている型について詳しく記載していきます。
any
any型は型のゴッドファーザーです。全ての値の集まりであり、anyを使えばなんでもできますが、TypeScriptの魔法である型チェッカーの真価を発揮できなくしてしまいます。anyを使うとJavaScriptでのプログラミングと大差ないものになってしまいます。本当に最後の手段として使用してください。
稀なケースですが、もしanyを使うとしたら以下のように使用します。
let a: any = 666; // 本体はnumber型
let b: any = ['aaaa']; // 本来はstring型の配列
let c = a + b; // ???
変数であるa
とb
を明示的にanyであるとことをTypeScriptに伝えることです。つまり、開発者が意図的にany型を使用している場合を除いてはできるだけ使用しないようにします。
また、明示的に型付けを行うことをアノテートすると呼びます。型アノテーションも同じ意味になります。
unknown
any
と同様に全ての値の集まりですが、1点だけ大きな違いがあります。
unknown型は、それが何であるかをチェックするまで値を使用することができない
具体的にコードでみてみましょう。
let a: unknown = 666; // unknown
let b = a === 666; // boolean
let c = a + 10; // エラー: 演算子 '+' を型 'unknown' および '10' に適用することはできません。
if(typeof a === 'number'){
let d = a + 10 // 演算可能になる!
}
c
を宣言した時にa + 10
を代入しようとしたらエラーになりますが、typeof a === 'number'
でa
がnumber型
であることをチェックできたら演算することができるようになります。このように、unknown型はその値が何であるかチェックされるまでは、値として使用することができません。なので、前もって型がわからない場合はunknown型の使用を推奨しています。
boolean
真偽値です。ご存知の通りかと思いますが、trur
かfalse
のどちらか一方の値をとります。具体的なコードでみてみましょう。
let a = true; // 1. 値がbooleanであることをTypeScriptに推論させる
const b = true; // 2. 値が特定のbooleanであることをTypeScriptに推論させる
let c: true = true; // 3. 値が特定のbooleanであることをTypeScriptに明示的に伝える
全てboolean型の変数の宣言です。一般的には2の方法を使用します。より安全である使い方は3ですが、使用頻度は低いです。また、3のような変数の宣言のことをリテラル型と呼びます。リテラル型はただ一つの値を表それ以外の値は受け入れない型のことです。この例でいうとc
はtrue
という値しか受け入れません。
number
ご存知の通り、全ての数値の集まりです。整数、流動小数点数、正数、負数、Infinity(無限大)、NaN(非数)などの値をとります。四則演算から比較(<
)を行うことができます。特筆することはありませんが、変数の宣言などはアノテートすることなく、TypeScriptに型を推論させます。
let a = 123; // let a: number と推論
const b = 456; // const b: 456 と推論、リテラル型
※bigint型という数値を表すもう一つの型もありますが、こちらに関しては割愛します。
利用頻度が少ないのと、全てのJavaScriptエンジンでネイティブにサポートされていないため。
オブジェクトの基本
構文によって作られる型の総称です。{}で囲われたものや、クラス、など総じてオブジェクトと呼びます。
まずは、明示的なアノテーションを持たせてオブジェクト型をみてみましょう。
let o: object = {
a: 'x'
}
o.a // エラー:プロパティ 'a' は型 'object' に存在しません。
上記コードのように明示的なアノテーションをするとアクセスできません。これは、object型
がnull
でなく何かしらのJavaScriptオブジェクトであることのみを伝えるからです。
では、object型
は全く意味のない型なのでしょうか。いいえ、オブジェクトリテラルを使用することで、宣言したオブジェクトにアクセスできるようになります。具体例をみてみましょう。
// オブジェクトリテラル
let o = {
a: 'x'
}
o.a // a: string
// オブジェクト型リテラル
let b: {a: string} = {
a: 'abc'
}
b.a // a: string
オブジェクトを宣言するときはJavaScriptのオブジェクトリテラルを使用します。この場合でもTypeScriptの型推論は機能します。また、オブジェクトの持つプロパティに型を付けて、宣言することをオブジェクト型リテラルと呼びます。明示的に各プロパティの型を宣言することができます。
オブジェクトの宣言方法とオプション
インデックスシグネチャ
いちいち型を記載するのを省くために、オブジェクトが持つキーと値のペアをいくつか含む場合に使用します。具体的なコードをみてみましょう。
let howManyPropaty: {[key: string]: number } = {
'a': 1,
'b': 2,
'c': 3
}
[key: string]: number
この部分がインデクスシグネチャです。これは、string型
のキーとnumber型
の値を持つプロパティが複数持つ可能性があるオブジェクトを宣言しています。ただし、キーは必ずstring型
かnumber型
でなければいけません。
様々なオプション
オブジェクトには様々なオプションがあります。具体例とともに一気に記載します。
プロパティのキーの後に?
をつけることでそのプロパティはオプショナル(あってもなくても良い)になる
let optional: {
a: string
b?: number // bはあってもなくても良い
c: boolean
}
optional = {
a: 'a',
b: 7,
c: false
}
// bが宣言されていないがエラーにならない
optional = {
a: 'a',
c: false
}
プロパティのキーの前にreadonly
をつけることでそのプロパティは読み取り専用になる
let sampleObject : {
readonly a: string // 読み取り専用プロパティ
b: number
c: boolean
}
sampleObject = {
a: 'a',
b: 7,
c: false
}
sampleObject.a = 'b' // エラー:'a'は読み取り専用プロパティであるため、割り当てることができません。
型エイリアス
型の別名のことです。変数宣言を使用して、開発者自身で名前をつけることができます。具体例をみてみましょう。
type Price = number;
type Coffee = {
price: Price // price: number
taste: string
}
単に型の別名を宣言しているだけですが、Coffee
が何を示しているのかがわかりやすくなります。また、型エイリアスに元の型を割り当てることもできます。(型に別の名前を付けただけなので当たり前ですが)
type Price = number;
type Coffee = {
price: Price // price: number
taste: string
}
let price = 66;
let iceCoffee: Coffee = {
price: price, // 割り当てるデータ型がPriceでなく、numberでもエラーにならない
taste: 'black'
}
合弁型(union型)
ある型とある型を組み合わせた型です。複数の方の和になります。
type Cat = {
name: string,
purrs: boolean
}
type Dog = {
name: string,
barks: boolean,
wags: boolean
}
type CatOrDog = Cat | Dog; // 合弁(union)型
// Cat型かDog型のどちらか、またはどちらのプロパティも含む場合エラーにならない
let a: CatOrDog = {
name: 'aaa',
purrs: true
}
// どちらの方も満たさない場合はエラーになる
let b: CatOrDog = {
name: 'aaa'
// 型 '{ name: string; }' を型 'CatOrDog' に割り当てることはできません。
// Type '{ name: string; }' is missing the following properties from type 'Dog': barks, wags
}
交差型
ある型とある型の両方のプロパティを全て持った型です。合弁(union)型と違って、割り当て全ての型のプロパティを持たなければいけません。
type Cat = {
name: string,
purrs: boolean
}
type Dog = {
name: string,
barks: boolean,
wags: boolean
}
type CatAndDog = Cat & Dog;
// Cat型とDog型のどちらのプロパティを満たす
let a: CatAndDog = {
name: 'aaa',
purrs: true,
barks: false,
wags: false
}
// Dog型のwagsがないのでエラーになる
let b: CatAndDog = {
name: 'aaa',
purrs: true,
barks: false
}
// 型 '{ name: string; purrs: true; barks: false; }' を型 'CatAndDog' に割り当てることはできません。
// Property 'wags' is missing in type '{ name: string; purrs: true; barks: false; }' but required in type 'Dog'.
タプル型
固定長な配列を表します。不均一なリストを安全なコードにするのに使用します。
let tupple: [number, string] = [1, "aaa"]
// numberとオプショナルなstringの配列を配列で持つデータ
let coffeeList: [number, string?][] = [
[200, "sweet"],
[230, "soy"],
[500, "premium"],
]
可変長引数
要素がいくつあるかわからないが、少なくとも1つ以上の要素を持つ引数や値をとる時に使用します。
// string型を1つ持ち、少なくとも1つ以上のnumber型を持つ配列
let beans: [string, ...number[]] = [
'mame', 20, 30, 40, 50
]
...
をつけると可変長引数になります。
読み取り専用の配列とタプル
タプル型の読み取り専用の値を宣言することができます。気泡はいくつかありますが、基本的に意味は同じです。
// 全て読み取り専用
let a: readonly[number, number, string] = [1, 1, 'b'];
let b: Readonly<[number, number, string]> = [1, 1, 'b'];
let c: ReadonlyArray<number> = [1, 1];
列挙型(enum)
ある型において取り得る値を列挙する順序づけられていないデータ構造で、キーが固定されているオブジェクトです。
宣言するときはenum 名前
で宣言することができます。
enum Lang {
Japan,
English,
Korea
}
ただし、列挙型は様々な問題を抱えています。cosntを宣言して使用しないと呼び出し側で存在しないプロパティにアクセスできてしまいエラーになったりします。列挙型を安全に使用するためには様々な落とし穴が伴うため、使用は控えることをおすすめします。
最後に
TypeScriptは様々な型をサポートします。ここに記載した型以外にもサポートしている型はあります。あくまで入門ということで一般的な型をご紹介しました。間違っているところなどあったらコメントなどで是非教えてください!
次回は関数について投稿しようと思いますので、読んでいただけると嬉しいです。